From 43c55cf6a4074ef5c4e6767ac66ef29644aa41f4 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Mon, 9 Dec 2019 18:09:44 +0200 Subject: [PATCH 01/56] 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 84b99b034af99..2527ffba2cbbd 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 b1d838aed992d..a6646ea338c93 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 2a7bd5476f0a5..a4fdfd7482f74 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 0000000000000..5d469606944a1 --- /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 eca6258099141..e54278698a05a 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 6ec0413968a55..2a37be7f3f46a 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 dd70c5646f708..202a509ee58c9 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 9a4bccc21db3e..8bfccd49bdff3 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 02/56] 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 58ee1668df701..2bbc39969370b 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 780a541db0890..0d3e2c10579f5 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 03/56] 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 847f09b4ab4cf..cf36d4ce884ac 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 dcbd65fbca520..aa16bcdb34d36 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 a496cccc42a58..ed39beeb7d088 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 ea5c245d9e39f..25bf3597c3466 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 b85591d13998a..49381e3b1c031 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 6137a21d0bd8d..39a87ece68ac9 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 ca7d8e3f31c58..b4fd521f21bec 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 c3216773c1a32..ac3f3fef97c70 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 bf309c65556a8..6ebc43bb92737 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 d36a587b9257f..5c35e9a23b8a1 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 09bcddef02cc3..6e2ef4a129397 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 526bb52c61afa..d149a33d3b20d 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 ddeb701c3ee31..f637c15553823 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 04bc51067d9df..887495a231485 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 3fd2f91b25f4a..cfa2fd650e83b 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 075a7867b4720..74e6341acc675 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 c6977b2150840..fc6faed170c9f 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 57249563f4566..6df7ab3d4243d 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 04/56] 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 fc83dc64aafab..1d798a4a2bc40 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 05/56] 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 8fcf3e6d62231..9c76e2b1ee84e 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 06/56] 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 fac9b9ce53184..a1c49ae03f359 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 5cb93a36cdd18..1e894664c2bf1 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 b500f5c79e98b..833ca8467292e 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 6ae84b9c641c2..150fae6e87dde 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 272544fa036cc..5935afec1dd70 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 5956b6c306b0e..1c3666ac0980c 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 e465149b301dc..97c0d5b89d657 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 d3f5fb1945254..fba249670ce60 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 43cf8c2a23286..eeddc390037a6 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 0000000000000..65601b5371815 --- /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 0000000000000..77d33a7bce3b6 --- /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 c7223a859ee37..3d64b6d2aa2bb 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 0000000000000..7c17f0c6bddc0 --- /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 0000000000000..ec93516df8349 --- /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 0000000000000..13a0cf4c891a3 --- /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 0000000000000..032a46439ba55 --- /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 9e94a52278ab2..4d55fce3d8a7d 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 9cf1e6c4ed373..0598c88c80ba7 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 9b1bfca284ee6..84c4c28aa2260 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 0000000000000..605ffdd6f2134 --- /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 bca4387b496fd..2abe13eb0e292 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 0000000000000..b9e07564a324c --- /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 0000000000000..dd4d77a681171 --- /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 6488ddd399b7b..114226b3a4570 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 0000000000000..73ca33ae914a9 --- /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 0000000000000..bad3b3ac36ef7 --- /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 a09f842e36713..d83afb195a492 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 cf1389fbab9f5..d5e90d12cccc9 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 0b677cbfd1f64..0000000000000 --- 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 12cecc956ab68..0000000000000 --- 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 a77a3f1f61f2c..0000000000000 --- 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 9ff82db27fefc..0000000000000 --- 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 d5e70c03d4259..b6952695cd910 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 a45fca426e2b4..e6363f2e164b4 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 b25b381eef67b..a63fd390ea3ca 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 b14531d8f6efd..9a3aed548dcc9 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 104d632e3ac3d..8448f9da19f45 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 d5ee0d8c6976e..173c28826436b 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 92c3ac20a2819..1d8ae6d425065 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 59b26c4592848..ae9e8756c7efc 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 4ed1937ebf782..e0db5f360f0f6 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 7517fa48ad8b8..0352902072790 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 7eddf3f4891e5..8807489157d71 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 c1672e65326aa..67d34960ed98e 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 f461100db01db..c30792b23e3ac 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 7461edcff10e9..1de5a10977f83 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 f8d6fdf85205e..5853bebe3c669 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 1b712e84d4a05..b04c9598559b3 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 b27be4d1715e8..f71be12276be5 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 43639641d541c..66ec38331c89a 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 179665ed11111..f659154c910f1 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 9b9a842689fd4..b9142c821051f 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 91dc516eebaad..109693bbb4b1b 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 f4b2be3420298..bf3435582ef47 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 07/56] [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 03bbddb998b3c..72cbd1b861a05 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 40d9d25869c7a..89d4224a7df14 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 08/56] [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 78863f91451f6..a4b4f500b8832 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 4f77098d6a456..ad9a714ee8d02 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 0000000000000..998fbb2c21f40 --- /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 0000000000000..29bfd07c525cd --- /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 09/56] 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 cf36d4ce884ac..edd2b62129627 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 ee65a1cf79148..7917297883b03 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 6df7ab3d4243d..cfef1bec7e6a0 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 10/56] 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 88d2d873521cb..d9a284d34de6b 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 06eb30db0b24b..2215bd8a95a1d 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 bc70339bd1a66..d72722af06bff 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 11/56] 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 0000000000000..1784a2650a95a --- /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 8c1d990c389b8..a93a91a42dbf3 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 e33040485bf47..ed91d391fc1fd 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 0000000000000..594b2641c39be --- /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 0000000000000..27be7925fe00c --- /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 e01240da87543..698d7bb48e685 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 fdc40768ebe41..22fbfe0e1ab08 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 0000000000000..af52192dd85e4 --- /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 a20a4f0634766..035983dc446dc 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 0000000000000..63c3a59044c1f --- /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 f4673af96b2cd..619903954ff55 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 0000000000000..ba8e43b0cea85 --- /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 4cf82a92d2cef..03367feb83ee4 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 0000000000000..2fc8ffef9713b --- /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 0000000000000..dfb575157d362 --- /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 1bd534bf74ff7..403ff2b79b55f 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 0000000000000..240d8fb26f3e0 --- /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 5411187cbcfd7..c8e1194a8f3cc 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 3c7d00a80fecf..9f90097e55475 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 0000000000000..3a121eb9da034 --- /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 f7164f0ad3c83..006e0623be913 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 4fcb15ccac44a..18285194c6054 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 56b63018b5153..45d59c97941b3 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 3d819bd145fa6..b300539f4280a 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 94b2941ecd3d1..58eef9fbf6b87 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 0000000000000..cab16252add4a --- /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 902490bebd1ac..28df897b67c09 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 adf0f2266ba17..9d494b1e6d950 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 12/56] [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 d82424c1c3bd3..7c22d6334a2d1 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 bed466dd9b94f..435a8d9bf8688 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 4418bbc52b57d..e673bb116e1dd 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 c55c99fb291c4..e1d10e2efdefb 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 5d3b47ecebfd5..858f3580f57e8 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 577de2ce0d532..488c34c945b48 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 cb1d59254923b..7f61765f5532c 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 d6b828642433d..a2d49b88a8f64 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 fc50e54e06e4e..83bf509fa7a93 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 9dedda6d79839..a7668f47b614c 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 c18891c032060..cd8b716221b9b 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 4aa57f005445b..b271af2db1e7d 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 31068dac5d23a..e14c889934537 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 14a061fd4ccb7..619c2eccf3d99 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 89f21bbada939..ca2905320d5b6 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 87b6ffb985c37..9d5aa07a33020 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 dd45490c778bf..f5147bc5a8f8b 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 d1a7a01c72415..6ed6fdd2577d8 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 d03d68417dd5d..dfa1275a6b26b 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 156756698435f..943c41fd6dea6 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 d20b4d213e9cc..4663ea357f259 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 4c5a5a6af93de..88f072f38b7e1 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 0000000000000..38b3ed9f74696 --- /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 0000000000000..681d66e16d0ba --- /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 0000000000000..ed8849831a479 --- /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 9ca4c32631334..721a172ce55d7 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 0000000000000..adb96ba398f5e --- /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 eb7f2ae03b64b..c754ab73ea21e 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 0000000000000..f9f5bf854e45c --- /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 0000000000000..e4da196007527 --- /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 0000000000000..61e68f886ffe7 --- /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 0000000000000..e812b031a28fd --- /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 b00a5929d9ef1..0000000000000 --- 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 657439104e306..0000000000000 --- 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 137cf7eedbccf..0000000000000 --- 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 b9160c95621ee..0000000000000 --- 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 364e7f00c9571..0000000000000 --- 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 de24263c6af5c..0000000000000 --- 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 9bf2b1abf5f90..0000000000000 --- 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 2381e9e259c07..0000000000000 --- 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 ee8fe1fc93fb3..0000000000000 --- 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 ed8f2e5745bea..0000000000000 --- 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 721644acd989d..0000000000000 --- 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 b733b6bb8c592..0000000000000 --- 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 df1b37f19bf29..0000000000000 --- 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 09ddfb1c34a92..0000000000000 --- 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 8a3c765519ef3..0000000000000 --- 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 0000000000000..bdd4f33c35c78 --- /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 0000000000000..0e0be24c00207 --- /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 0000000000000..c4aaf4aae93c2 --- /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 0000000000000..55f95e9644b8b --- /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 0000000000000..ee37c4cb784d1 --- /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 0000000000000..19801e7a98ac2 --- /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 0000000000000..a3dbf0f1b09af --- /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 0000000000000..8b6508c64dc5c --- /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 81ec19a4fd0ef..ced59c3ef2c7b 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 94f30bc9f92df..21fe7f7e2557a 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 0000000000000..607dcd963780c --- /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 0000000000000..97a5d31bb0133 --- /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 0000000000000..a94558143882b --- /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 0000000000000..bfe7c7f546fc3 --- /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 0000000000000..00966ddba7c7a --- /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 0000000000000..ad3c78183297d --- /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 0000000000000..56c9f151dc712 --- /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 0000000000000..285e9c94a76d9 --- /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 a43398bd6876a..0000000000000 --- 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 8e1abc7045602..aa22db965664a 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 13/56] 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 4ffe790bd51a1..1f5ed443fee80 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 3d34b1bc4a2d2..389057f94144d 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 282f4ee65dc96..68076cd43c336 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 dcc07f4fd43c5..16b22a164f2f0 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 7b0e42283d5f5..100fa92a39990 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 52bf347a94075..88e2fe812faa6 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 591833d646360..771984a1233e3 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 14/56] 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 b4d73cc4759d7..c0a01bc85e916 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 1c10d1b1a83af..1cbf3949d20f8 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 a378d8a4b9b55..dd2bd328ce53f 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 01853ee9cb870..0e9fe0483ee1e 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 1d453d2bd2340..def3b7eea21d9 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 ec45e298d3902..e1437b8eef554 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 f17430b734c66..c2af29051d8dd 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 8ec569a69ff23..29b89150e3990 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 b91fd86b0b682..06fe2fb0e591c 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 5ed67ae82b0ce..6767468509d25 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 1afb8a8870215..c532b76a904d5 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 a5bf42bc2cc01..41a7c17a02c5a 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 4cb28728fb421..28122c72baf65 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 cd5b9c7c4a7e8..b0ba5a8a0f566 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 5a74241bc4829..94b34034cd8b2 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 b9142c821051f..d51332c65aa54 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 109693bbb4b1b..974467a8d20d0 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 b2e69a2121825..30b235a784c22 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 197ad3ff80094..aacf0b8f87ed0 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 62c8b31209729..54f38d2fd747e 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 ae1464023c011..288eda5111a1e 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 53b11525d1b95..426bbe853ca19 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 15/56] [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 dc0756978010e..876993eed7795 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 ebb9fc27be149..0c4999ce4e41f 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 0342975ce3192..632ed22474022 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 b1f3c32f267b9..c646d0ac5c1df 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 dc5098c4d6d4d..6bd8b64ddf832 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 e339cad6af973..5f4139432a912 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 699955fe6542a..b8fc428a62a52 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 a25e3bf8684c9..1d6e6b256463e 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 53794f2043aad..426af96b63ba2 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 b834fb842389e..4a0363b5cb2e1 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 7e831115e6dba..60a6201ac872c 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 16/56] [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 fca4a13db8cb5..cb72fb4cfba46 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 901d27295479a..4e28605f6c0b2 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 790b3aaa83095..24589822f0250 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 d6db1c4ef5bb9..bb0958b32fa19 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 6e0c5e98206e4..9d5ac6db7bbb7 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 153fc032883ff..0000000000000 --- 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 83ddbf5cef0e4..0000000000000 --- 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 e14c889934537..a137d54250189 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 619c2eccf3d99..94c42664c281d 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 ca2905320d5b6..82fe0f55215fb 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 9d5aa07a33020..a8c4b7407c448 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 b4152256fbb53..99af43ce51a12 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 88f072f38b7e1..0d15fa1faa78f 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 dbeba598639b1..b1f0c3c4a3a18 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 88c46b18d515a..38fec7fc05a3c 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 ffa113a18c36c..dd31ed9fcaf5f 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 fc5c037dfbb5f..0d698f1e19213 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 291345b6befd3..48b9750ddd949 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 325aaa4fa4339..a3ea8548bddc2 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 719b9e8d6e18e..0c894c6980a31 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 1c388a1808c70..4a179073852b0 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 9c192eddf6f8a..11d007f591fac 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 542a2a0108a9a..eeea4bec2fb25 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 30e9ace2c0f19..e5fce811e560f 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 75c460f0807b4..d904219c76531 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 882a139eb739a..177eb05d8539d 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 3aeb137688594..17d203918d825 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 42ff7cca19964..aa620f6cf2590 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 515920ace28d8..04c1584f1204d 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 0ba0dfcb516fd..32a5c72215dda 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 c53805dc95fe7..e97a07e276dcf 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 9a799a90efc65..533b6c23088ec 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 ad19872b7a75d..989aaf2b6eeea 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 0000000000000..2bc33b87a1b43 --- /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 0000000000000..8518177cf6f96 --- /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 0000000000000..c675be691b47e --- /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 0000000000000..866f4d7575e2f --- /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 17/56] 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 888565cd7e0ff..c18c071ca7675 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 18/56] [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 f4cb43ad90146..de1b47ea28a91 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 4eb0a952defba..c897ee2a5b2d0 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 4279fbbe0fbb3..1d5815a84920c 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 19/56] 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 989bb6f754a09..230e511746022 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 98fa62021b5bb..a7bce739ba8e3 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 d1b7bc20dd9f9..0de9318ea0c27 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 1913301e21a76..e554859928c0b 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 ece13b72c93ea..0000000000000 --- 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 81cd5d0f86e9b..0000000000000 --- 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 89171e3565530..0000000000000 --- 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 c4aee5266d6fa..0000000000000 --- 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 9532983942d6b..0000000000000 --- 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 eeb0df2747a14..0000000000000 --- 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 27833d7594031..0000000000000 --- 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 6369a2a3dd5f3..da58881b5b96e 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 9fce720f5b14b..0c593bec1af3a 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 661b9c7a8841e..dc3c409ac497e 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 a54780267f1c1..a7dff2f11f232 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 01306901e9caa..834c991d5c6a4 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 59a4e8911a94d..95769414832cc 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 b9a03fdbc548b..0642c1fbe6186 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 a650a867aea60..91b73307ef56c 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 918c42f480e1e..eda80ca64c86f 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 ec70c54892a0e..547a55da7438b 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 cac14e654016f..b2f9442f48edb 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 aaa5142f519ac..e7e5e8aa64f7b 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 cfebca70d27b5..0265129d9ccab 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 1facc761d6379..589803b33e11c 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 1facc761d6379..589803b33e11c 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 955dc831e05e2..433616e422802 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 74e6341acc675..a92839eadefa4 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 20/56] [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 0000000000000..12b9e8a1cfb29 --- /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 adaded0832522..7c393689d166c 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 27768f784b1c5..ab53b0412ca35 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 235e597f8c280..b9e6822c8f41a 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 4b6f77262b7f9..8e71c16921078 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 21/56] [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 cd28114327b6d..41479cf6351ec 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 549ca4c1ae047..4cefbea7225ec 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 674c3f59ce957..a5b85788fdea9 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 0000000000000..83bae37c348d4 --- /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 0000000000000..3020ad7eb5f84 --- /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 0000000000000..75aea8c415eee --- /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 0000000000000..a737d19a5923d --- /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 39c088b1ceb5a..fa1ccb4efa4bb 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 b3254f597dfcf..0e778f35188f0 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 220eaade12fa6..2b60c6edd97aa 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 0000000000000..7cc8050aafd14 --- /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 12117d88f8283..da468b4391e4e 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 78893b83cd645..41ff3c293a713 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 beb5eb391d368..88212849d4594 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 70855101518d6..e2be0d64b8f1e 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 4df8e1368ca01..b5e48a4ec6214 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 4cfac85f00b15..e1a051355576f 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 17e06348d18b2..3edb289985d55 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 3b99e2d4f4379..2ca8be8e40d86 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 7a2fa86822c56..30fd4d3f77b5c 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 65b225d019603..2536ddbee401b 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 b38890afefb41..0000000000000 --- 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 3b3ff602731cc..0000000000000 --- 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 c2843320bfd0c..0000000000000 --- 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 02964bcb27e11..0000000000000 --- 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 0894a31996042..0000000000000 --- 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 8e00425526935..0000000000000 --- 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 ce3193e57ab09..0000000000000 --- 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 7651b039083cf..0000000000000 --- 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 c0d60c4d336de..0000000000000 --- 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 0306efc334a51..0000000000000 --- 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 8dbccf6c2bdd3..0000000000000 --- 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 2ab0a9a47ae86..0000000000000 --- 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 b1daa2bc8110d..0000000000000 --- 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 79a442789d6dd..aecba1779d036 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 d699db6af042e..601db0f56a693 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 0000000000000..3bd3d31c745a9 --- /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 0cbc94516617b..0000000000000 --- 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 22/56] 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 254a7ad8b3637..52fcc9813a93d 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 23/56] 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 f840c019d5e02..7a7446602351d 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 08607f04a5235..8ff54e25a0c99 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 3916ec1d62b6c..27fda9871e685 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 4f523f203f87a..d86a06767c9d1 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 318dbdf068d6a..634a797880812 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 fb82a03f172b3..cb5277ae19100 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 19618bc9e39fe..4d44ee9dfe6bd 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 7fc3f45911010..334fb2120319d 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 6aeedb93a1098..6e8f8557fb24a 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 e2460c549c05d..1bec2632d8082 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 edfb340bfd784..8f3996f958bb2 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 bf61ee2e3f137..c0a99ae068e71 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 31af7a0acffbb..359058f2ac23a 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 3018f8efffffe..929905a958abb 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 24/56] 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 5605ed5c56688..39c87d97af4ba 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 edd2b62129627..8ccb138a3aced 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 8ddeda93e6a7e..050d13b4b2c3e 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 4d9aba93d61db..254cce483cb7c 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 a78de8dfe17e4..a0cf3d1602879 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 380488ff9f62d..c58103cca5f8d 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 0c8faf47411d4..d1734e836d983 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 24fe4e1a4d986..a19a39da0f6dd 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 6e07757abf7bc..defbcd3331df4 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 0000000000000..646c89efe8e20 --- /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 0000000000000..c2100546364d4 --- /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 0000000000000..3662910ca5a7b --- /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 25/56] [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 350a3c2a997cf..77a2bfe77b4ab 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 3ff674c89682d..37ea49172d2c4 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 459c52997e229..fcba9691b1772 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 88995e9acc5fe..2d354938d7648 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 2d9629a436b3a..7f78bc28c6d3d 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 0000000000000..94dd76884f2ca --- /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 0000000000000..5c7fdb532faa1 --- /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 e65eb2f27d063..4eb45229c2234 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 2d98052b1062a..654f588fda858 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 a425251a29f36..326877919d985 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 f7b7885840455..16039d6fee833 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 e2be69d779799..cd12f1b75c828 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 71e2711906fce..0bd5d94f892b0 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 380c33e93ad90..90ff55fbebde5 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 50303fbad7aab..1a7abc6317075 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 fd31a4d8b6e4f..4ad97f8d98717 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 26/56] [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 cb72fb4cfba46..cf9fffc6a1455 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 8518177cf6f96..456646cc825f3 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 27/56] [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 435a8d9bf8688..4c113544e6e21 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 e673bb116e1dd..4cbf3756f58ac 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 488c34c945b48..9823d8b3b9bea 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 7f61765f5532c..c9d265ebffacd 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 a2d49b88a8f64..2eaa05ae2fa6a 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 83bf509fa7a93..41052ab4bbb15 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 a7668f47b614c..1787aa3a3081b 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 0d15fa1faa78f..6df4174e628b3 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 28/56] [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 33e820a5300c6..659ca36d84090 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 4f41ab5d4fad6..c7fd8600b73bb 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 bf9ac9b9bbe36..6190b92c9be3e 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 198fbe19a0b7a..d3651735c1a1d 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 ead77e6bc41d5..bc6d4d4c48466 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 065feae045597..d8792a761b186 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 9bc9ab99c4aff..7fe3678bb1f77 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 e5a723a99eafd..7ab60f8867c38 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 15ad9a33232ef..7c9efa280c9f1 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 0000000000000..71156bc38d498 --- /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 d27003f39d4c0..9c00947d7663c 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 d42c72f67f815..0000000000000 --- 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 0000000000000..5e9f2fdeb8999 --- /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 40d36dab227fa..36a9ecf3fcf8c 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 0000000000000..f47552e99a5c7 --- /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 0000000000000..6adcfd2cc7186 --- /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 edb7cccbd46a2..4511ac61f7396 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 f9e3a1a90115a..9b02be0581b8d 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 efab03303aa80..890fa64af9693 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 0000000000000..1aa2d70dabea6 --- /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 c83f7f5a5da8b..b6a3981215384 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 0000000000000..c64287a0e63b8 --- /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 4aa614b68ea23..5be5f58994887 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 99d9590e750fd..0dd2091bbfee0 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 420f0e5198056..0b46b562f2146 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 fa2ca6747bc40..92320f7bb443a 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 382f475669f5d..3093499a030c8 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 44c36f7d17d55..107cbc0e754b5 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 43d2962df0a1e..9df866d29a8a2 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 291d9feea3c40..80bee41175771 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 e4071c8b7d0ab..97a0f381f2aa4 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 49ec7acd6375d..972427e166afc 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 8093c57d2631a..8c6ddfebcb6cc 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 29/56] 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 0000000000000..4c7cdd18d03f6 --- /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 721c5d49634f4..501d83e36b1b9 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 30/56] [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 ed233c08e8d49..ce1419cd637b9 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 275dfa073fecd..f6982e13d7d03 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 a7f849704b5b2..b72bd27a31cf9 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 31/56] [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 3bf1d68590ec0..0c6acb8d9f46e 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 91e7b2c07070c..b7c8ddbc51b27 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 7b3b636d99b38..40a51f7c978e6 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 5f60ce38500c8..a2e31a0f33d39 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 0a97b596a7a71..b3c1de37c1340 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) => ( Date: Tue, 10 Dec 2019 13:16:23 +0100 Subject: [PATCH 32/56] [Console] Move out of quarantined (#52270) * WiP, lotta broken things, working through new editor.ts * RowParser -> TS * Utils to TS and regular module * Finished first version of core & sense editor wrappers. Tokenizer provider test working. Still need to delete some files * WiP - moved A LOT of code around and busy fixing sense-editor tests * Fix sense editor test * Clean up mocks * Moved A LOT of code out of quarantined. Still working on sense editor's integration test Not running yet. * WiP still finishing up manual testing * Fix use of Ace Range and fix open documentation * Move out of quarantined! * Remove load remote editor state for now * - fix use of token iterator - remove ace ranges from sense editor spec and fix spec :facepalm: * Address getSelectionRange document TODO Clean up use of jquery Remove use of `done` inside async tests (input_tokenization.test.js) Capitalize elasticsearch Introduce helper for converting to AceRange inside legacy core editor Update typings Clean up imports Cleaner variable assignment * Update src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts Co-Authored-By: Rory Hunter * Remove commented-out code lib/utils.js -> lib/utils.ts Rename engulfling range (sense_editor) * Rename format request function * utils.ts default export usage cleanup * Update replace regex and add another utils test * Remove legacy replace behaviour * Fix typo in comment --- src/legacy/core_plugins/console/index.ts | 18 +- .../load_remote_editor_state.test.ts | 96 --- .../load_remote_editor_state.ts | 53 -- .../editor/legacy/console_menu_actions.ts | 49 -- .../application/models/legacy_editor.test.ts | 69 -- .../application/models/legacy_editor.ts | 110 --- .../console/np_ready/server/.gitkeep | 0 .../core_plugins/console/public/README.md | 21 - .../console/{np_ready => public}/kibana.json | 3 +- .../console/{np_ready => }/public/legacy.ts | 36 +- .../application/components/console_menu.tsx | 4 +- .../application/components/editor_example.tsx | 12 +- .../application/components/help_panel.tsx | 0 .../np_ready}/application/components/index.ts | 0 .../application/components/settings_modal.tsx | 0 .../__snapshots__/split_panel.test.tsx.snap | 0 .../split_panel/components/resizer.tsx | 0 .../split_panel/containers/panel.tsx | 0 .../containers/panel_container.tsx | 0 .../components/split_panel/context.tsx | 0 .../components/split_panel/index.ts | 0 .../components/split_panel/registry.ts | 0 .../split_panel/split_panel.test.tsx | 2 +- .../application/components/top_nav_menu.tsx | 0 .../application/components/welcome_panel.tsx | 0 .../application/constants/help_example.txt | 0 .../console_history/console_history.tsx | 2 +- .../console_history/history_viewer.tsx | 18 +- .../containers/console_history/index.ts | 0 .../application/containers/editor/editor.tsx | 0 .../application/containers/editor/index.ts | 0 .../console_editor/apply_editor_settings.ts} | 31 +- .../console_editor/editor.test.mock.tsx | 23 +- .../legacy/console_editor/editor.test.tsx | 0 .../editor/legacy/console_editor/editor.tsx | 58 +- .../legacy/console_editor/editor_output.tsx | 28 +- .../editor/legacy/console_editor/index.ts | 0 .../console_editor/keyboard_shortcuts.ts | 53 +- .../editor/legacy/console_menu_actions.ts | 52 ++ .../containers/editor/legacy/index.ts | 0 .../subscribe_console_resize_checker.ts | 0 .../legacy/use_ui_ace_keyboard_mode.tsx | 0 .../np_ready}/application/containers/index.ts | 0 .../containers/main/get_top_nav.ts | 0 .../application/containers/main/index.ts | 0 .../application/containers/main/main.tsx | 0 .../application/containers/settings.tsx | 2 +- .../contexts/create_use_context.ts | 0 .../editor_context/editor_context.tsx | 0 .../editor_context/editor_registry.ts | 8 +- .../contexts/editor_context/index.ts | 0 .../np_ready}/application/contexts/index.ts | 0 .../application/contexts/request_context.tsx | 0 .../application/contexts/services_context.tsx | 1 + .../np_ready}/application/factories/index.ts | 0 .../application/factories/token_iterator.ts | 0 .../np_ready}/application/hooks/index.ts | 0 .../use_restore_request_from_history/index.ts | 0 .../restore_request_from_history.ts | 33 +- .../use_restore_request_from_history.ts | 0 .../use_send_current_request_to_es/index.ts | 0 .../send_request_to_es.ts | 5 +- .../use_send_current_request_to_es.ts | 4 +- .../application/hooks/use_set_input_editor.ts | 0 .../np_ready}/application/index.tsx | 6 +- .../np_ready}/application/logo.svg | 0 .../np_ready/application/models/index.ts | 21 + .../__tests__}/input_tokenization.test.js | 74 +- .../__tests__}/output_tokenization.test.js | 19 +- .../models/legacy_core_editor/create.ts | 30 + .../legacy_core_editor/create_readonly.ts} | 33 +- .../models/legacy_core_editor/index.ts} | 13 +- .../legacy_core_editor.test.mocks.ts} | 29 +- .../legacy_core_editor/legacy_core_editor.ts | 340 +++++++++ .../models/legacy_core_editor}/mode/input.js | 0 .../mode/input_highlight_rules.js | 0 .../models/legacy_core_editor}/mode/output.js | 0 .../mode/output_highlight_rules.js | 0 .../models/legacy_core_editor}/mode/script.js | 0 .../mode/script_highlight_rules.js | 0 .../legacy_core_editor}/mode/worker/index.js | 0 .../legacy_core_editor}/mode/worker/worker.js | 0 .../mode/x_json_highlight_rules.js | 0 .../legacy_core_editor/smart_resize.ts} | 3 +- .../legacy_core_editor}/theme_sense_dark.js | 0 .../sense_editor/__tests__}/editor_input1.txt | 0 .../__tests__}/integration.test.js | 331 ++++----- .../__tests__/sense_editor.test.js} | 125 ++-- .../application/models/sense_editor/create.ts | 32 + .../application/models/sense_editor/curl.ts | 204 ++++++ .../application/models/sense_editor/index.ts | 23 + .../sense_editor/sense_editor.test.mocks.ts | 32 + .../models/sense_editor/sense_editor.ts | 502 +++++++++++++ .../np_ready}/application/stores/editor.ts | 0 .../np_ready}/application/stores/request.ts | 0 .../application/styles}/_app.scss | 0 .../application/styles/components}/_help.scss | 0 .../styles/components}/_history.scss | 0 .../application/styles/components/_index.scss | 2 + .../application/styles}/index.scss | 4 +- .../public => public/np_ready}/index.ts | 2 +- .../np_ready}/lib/ace_token_provider/index.ts | 0 .../ace_token_provider/token_provider.test.ts | 40 +- .../lib/ace_token_provider/token_provider.ts | 0 .../__tests__}/url_autocomplete.test.js | 10 +- .../__tests__}/url_params.test.js | 6 +- .../lib/autocomplete}/autocomplete.ts | 77 +- .../lib}/autocomplete/body_completer.js | 0 .../components/accept_endpoint_component.js | 0 .../components/autocomplete_component.js | 0 .../components/conditional_proxy.js | 0 .../components/constant_component.js | 0 .../field_autocomplete_component.js | 2 +- .../components/global_only_component.js | 0 .../components/id_autocomplete_component.js | 0 .../lib}/autocomplete/components/index.js | 0 .../index_autocomplete_component.js | 2 +- .../autocomplete/components/list_component.js | 0 .../components/object_component.js | 2 +- .../components/shared_component.js | 0 .../components/simple_param_component.js | 0 .../template_autocomplete_component.js | 2 +- .../components/type_autocomplete_component.js | 2 +- .../components/url_pattern_matcher.js | 2 +- .../username_autocomplete_component.js | 2 +- .../lib}/autocomplete/engine.js | 0 .../lib}/autocomplete/url_params.js | 0 .../__tests__}/curl_parsing.test.js | 2 +- .../curl_parsing/__tests__}/curl_parsing.txt | 0 .../src => np_ready/lib/curl_parsing}/curl.js | 0 .../lib/es/__tests__}/content_type.test.js | 2 +- .../src => np_ready/lib/es}/es.js | 0 .../lib/kb/__tests__}/kb.test.js | 13 +- .../src => np_ready/lib}/kb/api.js | 0 .../np_ready/lib/kb/index.js} | 2 +- .../src => np_ready/lib/kb}/kb.js | 4 +- .../lib/mappings/__tests__}/mapping.test.js | 7 +- .../src => np_ready/lib/mappings}/mappings.js | 4 +- .../console/public/np_ready/lib/row_parser.ts | 151 ++++ .../np_ready}/lib/token_iterator/index.ts | 0 .../lib/token_iterator/token_iterator.test.ts | 0 .../lib/token_iterator/token_iterator.ts | 0 .../lib/utils/__tests__}/utils.test.js | 15 +- .../__tests__}/utils_string_collapsing.txt | 0 .../__tests__}/utils_string_expanding.txt | 12 + .../utils.js => np_ready/lib/utils/utils.ts} | 59 +- .../public => public/np_ready}/plugin.ts | 10 +- .../np_ready}/services/history.ts | 0 .../np_ready}/services/index.ts | 0 .../np_ready}/services/settings.ts | 0 .../np_ready}/services/storage.ts | 0 .../np_ready}/types/common.ts | 0 .../np_ready}/types/core_editor.ts | 100 +++ .../public => public/np_ready}/types/index.ts | 0 .../public => public/np_ready}/types/token.ts | 0 .../np_ready}/types/tokens_provider.ts | 0 .../quarantined/src/directives/_index.scss | 2 - .../console/public/quarantined/src/input.ts | 74 -- .../quarantined/src/sense_editor/editor.js | 676 ------------------ .../src/sense_editor/row_parser.js | 147 ---- 160 files changed, 2094 insertions(+), 1949 deletions(-) delete mode 100644 src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts delete mode 100644 src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts delete mode 100644 src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts delete mode 100644 src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts delete mode 100644 src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts delete mode 100644 src/legacy/core_plugins/console/np_ready/server/.gitkeep delete mode 100644 src/legacy/core_plugins/console/public/README.md rename src/legacy/core_plugins/console/{np_ready => public}/kibana.json (59%) rename src/legacy/core_plugins/console/{np_ready => }/public/legacy.ts (62%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/console_menu.tsx (98%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/editor_example.tsx (80%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/help_panel.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/settings_modal.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/components/resizer.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/containers/panel.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/containers/panel_container.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/context.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/registry.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/split_panel/split_panel.test.tsx (98%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/top_nav_menu.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/components/welcome_panel.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/constants/help_example.txt (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/console_history/console_history.tsx (98%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/console_history/history_viewer.tsx (83%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/console_history/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/editor.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/index.ts (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/__tests__/utils.js => np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts} (56%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/editor.test.mock.tsx (75%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/editor.test.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/editor.tsx (78%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/editor_output.tsx (78%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts (55%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/subscribe_console_resize_checker.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/editor/legacy/use_ui_ace_keyboard_mode.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/main/get_top_nav.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/main/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/main/main.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/containers/settings.tsx (98%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/create_use_context.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/editor_context/editor_context.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/editor_context/editor_registry.ts (85%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/editor_context/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/request_context.tsx (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/contexts/services_context.tsx (98%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/factories/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/factories/token_iterator.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_restore_request_from_history/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_restore_request_from_history/restore_request_from_history.ts (59%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_send_current_request_to_es/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_send_current_request_to_es/send_request_to_es.ts (96%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts (94%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/hooks/use_set_input_editor.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/index.tsx (91%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/logo.svg (100%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/index.ts rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/application/models/legacy_core_editor/__tests__}/input_tokenization.test.js (86%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/application/models/legacy_core_editor/__tests__}/output_tokenization.test.js (89%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts rename src/legacy/core_plugins/console/public/{quarantined/src/output.js => np_ready/application/models/legacy_core_editor/create_readonly.ts} (70%) rename src/legacy/core_plugins/console/{np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts => public/np_ready/application/models/legacy_core_editor/index.ts} (75%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src/setup_mocks.js => np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts} (69%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/input.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/input_highlight_rules.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/output.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/output_highlight_rules.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/script.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/script_highlight_rules.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/worker/index.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/worker/worker.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/mode/x_json_highlight_rules.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/smart_resize.js => np_ready/application/models/legacy_core_editor/smart_resize.ts} (92%) rename src/legacy/core_plugins/console/public/{quarantined/src/sense_editor => np_ready/application/models/legacy_core_editor}/theme_sense_dark.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/application/models/sense_editor/__tests__}/editor_input1.txt (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/application/models/sense_editor/__tests__}/integration.test.js (80%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src/editor.test.js => np_ready/application/models/sense_editor/__tests__/sense_editor.test.js} (77%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/stores/editor.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/application/stores/request.ts (100%) rename src/legacy/core_plugins/console/public/{quarantined => np_ready/application/styles}/_app.scss (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/directives => np_ready/application/styles/components}/_help.scss (100%) rename src/legacy/core_plugins/console/public/{quarantined/src/directives => np_ready/application/styles/components}/_history.scss (100%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss rename src/legacy/core_plugins/console/public/{quarantined => np_ready/application/styles}/index.scss (81%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/index.ts (93%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/ace_token_provider/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/ace_token_provider/token_provider.test.ts (84%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/ace_token_provider/token_provider.ts (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/autocomplete/__tests__}/url_autocomplete.test.js (98%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/autocomplete/__tests__}/url_params.test.js (94%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib/autocomplete}/autocomplete.ts (94%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/body_completer.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/accept_endpoint_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/autocomplete_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/conditional_proxy.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/constant_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/field_autocomplete_component.js (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/global_only_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/id_autocomplete_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/index.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/index_autocomplete_component.js (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/list_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/object_component.js (98%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/shared_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/simple_param_component.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/template_autocomplete_component.js (95%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/type_autocomplete_component.js (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/url_pattern_matcher.js (99%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/components/username_autocomplete_component.js (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/engine.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/autocomplete/url_params.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/curl_parsing/__tests__}/curl_parsing.test.js (97%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/curl_parsing/__tests__}/curl_parsing.txt (100%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib/curl_parsing}/curl.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/es/__tests__}/content_type.test.js (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib/es}/es.js (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/kb/__tests__}/kb.test.js (95%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib}/kb/api.js (100%) rename src/legacy/core_plugins/console/{np_ready/public/application/models/index.ts => public/np_ready/lib/kb/index.js} (96%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib/kb}/kb.js (98%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/mappings/__tests__}/mapping.test.js (97%) rename src/legacy/core_plugins/console/public/{quarantined/src => np_ready/lib/mappings}/mappings.js (98%) create mode 100644 src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/token_iterator/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/token_iterator/token_iterator.test.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/lib/token_iterator/token_iterator.ts (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/utils/__tests__}/utils.test.js (91%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/utils/__tests__}/utils_string_collapsing.txt (100%) rename src/legacy/core_plugins/console/public/{quarantined/tests/src => np_ready/lib/utils/__tests__}/utils_string_expanding.txt (79%) rename src/legacy/core_plugins/console/public/{quarantined/src/utils.js => np_ready/lib/utils/utils.ts} (77%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/plugin.ts (90%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/services/history.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/services/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/services/settings.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/services/storage.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/types/common.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/types/core_editor.ts (62%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/types/index.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/types/token.ts (100%) rename src/legacy/core_plugins/console/{np_ready/public => public/np_ready}/types/tokens_provider.ts (100%) delete mode 100644 src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss delete mode 100644 src/legacy/core_plugins/console/public/quarantined/src/input.ts delete mode 100644 src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js delete mode 100644 src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts index c4e6a77b7d859..cec6d0a4219ae 100644 --- a/src/legacy/core_plugins/console/index.ts +++ b/src/legacy/core_plugins/console/index.ts @@ -19,7 +19,7 @@ import Boom from 'boom'; import { first } from 'rxjs/operators'; -import { resolve, join, sep } from 'path'; +import { resolve, join } from 'path'; import url from 'url'; import { has, isEmpty, head, pick } from 'lodash'; @@ -51,9 +51,7 @@ function filterHeaders(originalHeaders: any, headersToKeep: any) { // eslint-disable-next-line export default function(kibana: any) { - const modules = resolve(__dirname, 'public/webpackShims/'); - const quarantinedSrc = resolve(__dirname, 'public/quarantined/src/'); - const npSrc = resolve(__dirname, 'np_ready/public'); + const npSrc = resolve(__dirname, 'public/np_ready'); let defaultVars: any; return new kibana.Plugin({ @@ -180,16 +178,10 @@ export default function(kibana: any) { }, uiExports: { - devTools: [`${npSrc}/legacy`], - styleSheetPaths: resolve(__dirname, 'public/quarantined/index.scss'), - + devTools: [resolve(__dirname, 'public/legacy')], + styleSheetPaths: resolve(npSrc, 'application/styles/index.scss'), injectDefaultVars: () => defaultVars, - - noParse: [ - join(modules, 'ace' + sep), - join(modules, 'moment_src/moment' + sep), - join(quarantinedSrc, 'sense_editor/mode/worker.js'), - ], + noParse: [join(npSrc, 'application/models/legacy_core_editor/mode/worker/worker.js')], }, } as any); } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts deleted file mode 100644 index 0eb6a8fec4f86..0000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import $ from 'jquery'; - -import { loadRemoteState } from './load_remote_editor_state'; - -describe('[Legacy Console] loading remote editor state', () => { - const sandbox = sinon.createSandbox(); - - let inputMock: any; - let ajaxDoneStub: any; - beforeEach(() => { - ajaxDoneStub = sinon.stub(); - sandbox.stub($, 'ajax').returns({ done: ajaxDoneStub } as any); - - inputMock = { - update: sinon.stub(), - moveToNextRequestEdge: sinon.stub(), - highlightCurrentRequestsAndUpdateActionBar: sinon.stub(), - updateActionsBar: sinon.stub(), - getSession: sinon.stub().returns({ on() {} }), - }; - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('correctly loads state from any external HTTPS links.', () => { - const mockContent = {}; - ajaxDoneStub.yields(mockContent); - - loadRemoteState({ - input: inputMock, - url: 'https://state.link.com/content', - }); - - sinon.assert.calledOnce($.ajax as any); - sinon.assert.calledWithExactly($.ajax as any, { - url: 'https://state.link.com/content', - dataType: 'text', - kbnXsrfToken: false, - headers: {}, - }); - - sinon.assert.calledTwice(inputMock.moveToNextRequestEdge); - sinon.assert.calledWithExactly(inputMock.moveToNextRequestEdge, true); - sinon.assert.calledOnce(inputMock.highlightCurrentRequestsAndUpdateActionBar); - sinon.assert.calledOnce(inputMock.updateActionsBar); - sinon.assert.calledOnce(inputMock.update); - sinon.assert.calledWithExactly(inputMock.update, sinon.match.same(mockContent)); - }); - - it('correctly loads state from GitHub API HTTPS links.', () => { - const mockContent = {}; - ajaxDoneStub.yields(mockContent); - - loadRemoteState({ - input: inputMock, - url: 'https://api.github.com/content', - }); - - sinon.assert.calledOnce($.ajax as any); - sinon.assert.calledWithExactly($.ajax as any, { - url: 'https://api.github.com/content', - dataType: 'text', - kbnXsrfToken: false, - headers: { Accept: 'application/vnd.github.v3.raw' }, - }); - - sinon.assert.calledTwice(inputMock.moveToNextRequestEdge); - sinon.assert.calledWithExactly(inputMock.moveToNextRequestEdge, true); - sinon.assert.calledOnce(inputMock.highlightCurrentRequestsAndUpdateActionBar); - sinon.assert.calledOnce(inputMock.updateActionsBar); - sinon.assert.calledOnce(inputMock.update); - sinon.assert.calledWithExactly(inputMock.update, sinon.match.same(mockContent)); - }); -}); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts deleted file mode 100644 index b2f475036c5da..0000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/load_remote_editor_state.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; - -// @ts-ignore -// import mappings from '../../../../../../public/quarantined/src/mappings'; - -/* eslint-disable no-console */ - -export interface InitializationArgs { - url: string; - input: any; -} - -export function loadRemoteState({ url, input }: InitializationArgs) { - const loadFrom = { - url, - // Having dataType here is required as it doesn't allow jQuery to `eval` content - // coming from the external source thereby preventing XSS attack. - dataType: 'text', - kbnXsrfToken: false, - headers: {}, - }; - - if (/https?:\/\/api.github.com/.test(url)) { - loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' }; - } - - $.ajax(loadFrom).done(data => { - input.update(data); - input.moveToNextRequestEdge(true); - input.highlightCurrentRequestsAndUpdateActionBar(); - input.updateActionsBar(); - }); - input.moveToNextRequestEdge(true); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts deleted file mode 100644 index b728df615fc9d..0000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_menu_actions.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore -import { getEndpointFromPosition } from '../../../../../../public/quarantined/src/autocomplete'; - -export function autoIndent(editor: any, event: any) { - editor.autoIndent(); - event.preventDefault(); - editor.focus(); -} - -export function getDocumentation(editor: any, docLinkVersion: string): Promise { - return new Promise(resolve => { - editor.getRequestsInRange((requests: any) => { - if (!requests || requests.length === 0) { - resolve(null); - return; - } - const position = requests[0].range.end; - position.column = position.column - 1; - const endpoint = getEndpointFromPosition(editor, position, editor.parser); - if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) { - const nextDocumentation = endpoint.documentation - .replace('/master/', `/${docLinkVersion}/`) - .replace('/current/', `/${docLinkVersion}/`); - resolve(nextDocumentation); - } else { - resolve(null); - } - }); - }); -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts deleted file mode 100644 index 0dc1bcc96ddee..0000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts +++ /dev/null @@ -1,69 +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 { Range as AceRange } from 'brace'; -import { LegacyEditor } from './legacy_editor'; - -describe('Legacy Editor', () => { - const aceMock: any = { - getValue() { - return 'ok'; - }, - - getCursorPosition() { - return { - row: 1, - column: 1, - }; - }, - - getSession() { - return { - replace(range: AceRange, value: string) {}, - getLine(n: number) { - return 'line'; - }, - doc: { - getTextRange(r: any) { - return ''; - }, - }, - getState(n: number) { - return n; - }, - }; - }, - }; - - // This is to ensure that we are correctly importing Ace's Range component - it('smoke tests for updates to ranges', () => { - const legacyEditor = new LegacyEditor(aceMock); - legacyEditor.getValueInRange({ - start: { lineNumber: 1, column: 1 }, - end: { lineNumber: 2, column: 2 }, - }); - legacyEditor.replace( - { - start: { lineNumber: 1, column: 1 }, - end: { lineNumber: 2, column: 2 }, - }, - 'test!' - ); - }); -}); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts deleted file mode 100644 index f8c3f425a1032..0000000000000 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts +++ /dev/null @@ -1,110 +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 ace from 'brace'; -import { Editor as IAceEditor } from 'brace'; -import { CoreEditor, Position, Range, Token, TokensProvider } from '../../types'; -import { AceTokensProvider } from '../../lib/ace_token_provider'; - -const _AceRange = ace.acequire('ace/range').Range; - -export class LegacyEditor implements CoreEditor { - constructor(private readonly editor: IAceEditor) {} - - getLineState(lineNumber: number) { - const session = this.editor.getSession(); - return session.getState(lineNumber - 1); - } - - getValueInRange({ start, end }: Range): string { - const session = this.editor.getSession(); - const aceRange = new _AceRange( - start.lineNumber - 1, - start.column - 1, - end.lineNumber - 1, - end.column - 1 - ); - return session.doc.getTextRange(aceRange); - } - - getTokenProvider(): TokensProvider { - return new AceTokensProvider(this.editor.getSession()); - } - - getValue(): string { - return this.editor.getValue(); - } - - getLineValue(lineNumber: number): string { - const session = this.editor.getSession(); - return session.getLine(lineNumber - 1); - } - - getCurrentPosition(): Position { - const cursorPosition = this.editor.getCursorPosition(); - return { - lineNumber: cursorPosition.row + 1, - column: cursorPosition.column + 1, - }; - } - - clearSelection(): void { - this.editor.clearSelection(); - } - - getTokenAt(pos: Position): Token | null { - const provider = this.getTokenProvider(); - return provider.getTokenAt(pos); - } - - insert(valueOrPos: string | Position, value?: string): void { - if (typeof valueOrPos === 'string') { - this.editor.insert(valueOrPos); - return; - } - const document = this.editor.getSession().getDocument(); - document.insert( - { - column: valueOrPos.column - 1, - row: valueOrPos.lineNumber - 1, - }, - value || '' - ); - } - - moveCursorToPosition(pos: Position): void { - this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 }); - } - - replace({ start, end }: Range, value: string): void { - const aceRange = new _AceRange( - start.lineNumber - 1, - start.column - 1, - end.lineNumber - 1, - end.column - 1 - ); - const session = this.editor.getSession(); - session.replace(aceRange, value); - } - - getLines(startLine: number, endLine: number): string[] { - const session = this.editor.getSession(); - return session.getLines(startLine - 1, endLine - 1); - } -} diff --git a/src/legacy/core_plugins/console/np_ready/server/.gitkeep b/src/legacy/core_plugins/console/np_ready/server/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/legacy/core_plugins/console/public/README.md b/src/legacy/core_plugins/console/public/README.md deleted file mode 100644 index 3a53c46143793..0000000000000 --- a/src/legacy/core_plugins/console/public/README.md +++ /dev/null @@ -1,21 +0,0 @@ -## New Platform (NP) Ready vs Quarantined - -We want to move toward more modularised code in the Console app. -Part of the effort means separating out different console components -like: - -- The language parser -- The editor rendering component -- Autocomplete -- The UI container components - -In addition to this effort we want to bring Console in line with NP -requirements and ensure that we are not using angular and public ui -in this app anymore. - -The quarantined folder contains all of the code that has not been cleared -for living in the new platform as it has not been properly refactored -or has dependencies on, for example, UI public. - -Over time, the quarantined part of the code should shrink to nothing -and we should only have NP ready code. diff --git a/src/legacy/core_plugins/console/np_ready/kibana.json b/src/legacy/core_plugins/console/public/kibana.json similarity index 59% rename from src/legacy/core_plugins/console/np_ready/kibana.json rename to src/legacy/core_plugins/console/public/kibana.json index ed8e4d8f15830..3363af353912a 100644 --- a/src/legacy/core_plugins/console/np_ready/kibana.json +++ b/src/legacy/core_plugins/console/public/kibana.json @@ -2,5 +2,6 @@ "id": "console", "version": "kibana", "server": true, - "ui": true + "ui": true, + "requiredPlugins": ["home"] } diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/public/legacy.ts similarity index 62% rename from src/legacy/core_plugins/console/np_ready/public/legacy.ts rename to src/legacy/core_plugins/console/public/legacy.ts index 758ea81be88ad..c456d777187aa 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/public/legacy.ts @@ -17,35 +17,35 @@ * under the License. */ -import 'brace'; -import 'brace/ext/language_tools'; -import 'brace/ext/searchbox'; -import 'brace/mode/json'; -import 'brace/mode/text'; - -/* eslint-disable @kbn/eslint/no-restricted-paths */ import { npSetup, npStart } from 'ui/new_platform'; import { I18nContext } from 'ui/i18n'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ +import chrome from 'ui/chrome'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; export interface XPluginSet { dev_tools: DevToolsSetup; home: HomePublicPluginSetup; __LEGACY: { I18nContext: any; + elasticsearchUrl: string; + category: FeatureCatalogueCategory; }; } -import { plugin } from '.'; -import { DevToolsSetup } from '../../../../../plugins/dev_tools/public'; -import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; +import { plugin } from './np_ready'; +import { DevToolsSetup } from '../../../../plugins/dev_tools/public'; +import { HomePublicPluginSetup } from '../../../../plugins/home/public'; const pluginInstance = plugin({} as any); -pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - I18nContext, - }, -}); -pluginInstance.start(npStart.core); +(async () => { + await pluginInstance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + elasticsearchUrl: chrome.getInjected('elasticsearchUrl'), + I18nContext, + category: FeatureCatalogueCategory.ADMIN, + }, + }); + await pluginInstance.start(npStart.core); +})(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx index fd87c0b94ef5b..7842c15be267f 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/console_menu.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/console_menu.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; interface Props { - getCurl: (cb: (text: string) => void) => void; + getCurl: () => Promise; getDocumentation: () => Promise; autoIndent: (ev?: React.MouseEvent) => void; addNotification?: (opts: { title: string }) => void; @@ -48,7 +48,7 @@ export class ConsoleMenu extends Component { mouseEnter = () => { if (this.state.isPopoverOpen) return; - this.props.getCurl(text => { + this.props.getCurl().then(text => { this.setState({ curlCode: text }); }); }; diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx similarity index 80% rename from src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx index 01bd3fcd78e53..ea08a06a9e39b 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/editor_example.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_example.tsx @@ -20,9 +20,7 @@ import React, { useEffect } from 'react'; // @ts-ignore import exampleText from 'raw-loader!../constants/help_example.txt'; -import $ from 'jquery'; -// @ts-ignore -import SenseEditor from '../../../../public/quarantined/src/sense_editor/editor'; +import { createReadOnlyAceEditor } from '../models/legacy_core_editor'; interface EditorExampleProps { panel: string; @@ -32,11 +30,9 @@ export function EditorExample(props: EditorExampleProps) { const elemId = `help-example-${props.panel}`; useEffect(() => { - const el = $(`#${elemId}`); - el.text(exampleText.trim()); - const editor = new SenseEditor(el); - editor.setReadOnly(true); - editor.$blockScrolling = Infinity; + const el = document.querySelector(`#${elemId}`)!; + el.textContent = exampleText.trim(); + const editor = createReadOnlyAceEditor(el); return () => { editor.destroy(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/help_panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/help_panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/help_panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/help_panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/settings_modal.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/settings_modal.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/settings_modal.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/settings_modal.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/components/resizer.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/components/resizer.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/components/resizer.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/components/resizer.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel_container.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/containers/panel_container.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/context.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/context.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/context.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/registry.ts rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx index e60912a29355b..304535421a78a 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/components/split_panel/split_panel.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx @@ -22,7 +22,7 @@ import { mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import { spy } from 'sinon'; -import { PanelsContainer, Panel } from '.'; +import { PanelsContainer, Panel } from './index'; const testComponentA =

A

; const testComponentB =

B

; diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/top_nav_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/top_nav_menu.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/components/welcome_panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/welcome_panel.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/components/welcome_panel.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/components/welcome_panel.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/constants/help_example.txt b/src/legacy/core_plugins/console/public/np_ready/application/constants/help_example.txt similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/constants/help_example.txt rename to src/legacy/core_plugins/console/public/np_ready/application/constants/help_example.txt diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx index 30966a2f77e1d..8ec8b9c61bf03 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/console_history.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/console_history.tsx @@ -175,7 +175,7 @@ export function ConsoleHistory({ close }: Props) { role="option" onMouseEnter={() => setViewingReq(req)} onMouseLeave={() => setViewingReq(selectedReq.current)} - onDoubleClick={restoreRequestFromHistory} + onDoubleClick={() => restoreRequestFromHistory(selectedReq.current)} aria-label={i18n.translate('console.historyPage.itemOfRequestListAriaLabel', { defaultMessage: 'Request: {historyItem}', values: { historyItem: reqDescription }, diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx similarity index 83% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx index 6fbb46bba6212..2a5ffee149b92 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/history_viewer.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/history_viewer.tsx @@ -19,13 +19,14 @@ import React, { useEffect, useRef } from 'react'; import { i18n } from '@kbn/i18n'; -import $ from 'jquery'; import { DevToolsSettings } from '../../../services'; import { subscribeResizeChecker } from '../editor/legacy/subscribe_console_resize_checker'; // @ts-ignore -import SenseEditor from '../../../../../public/quarantined/src/sense_editor/editor'; +import * as InputMode from '../../models/legacy_core_editor/mode/input'; +const inputMode = new InputMode.Mode(); +import * as editor from '../../models/legacy_core_editor'; import { applyCurrentSettings } from '../editor/legacy/console_editor/apply_editor_settings'; interface Props { @@ -35,13 +36,11 @@ interface Props { export function HistoryViewer({ settings, req }: Props) { const divRef = useRef(null); - const viewerRef = useRef(null); + const viewerRef = useRef(null); useEffect(() => { - const viewer = new SenseEditor($(divRef.current!)); + const viewer = editor.createReadOnlyAceEditor(divRef.current!); viewerRef.current = viewer; - viewer.renderer.setShowPrintMargin(false); - viewer.$blockScrolling = Infinity; const unsubscribe = subscribeResizeChecker(divRef.current!, viewer); return () => unsubscribe(); }, []); @@ -54,13 +53,14 @@ export function HistoryViewer({ settings, req }: Props) { const { current: viewer } = viewerRef; if (req) { const s = req.method + ' ' + req.endpoint + '\n' + (req.data || ''); - viewer.setValue(s); + viewer.update(s, inputMode); viewer.clearSelection(); } else { - viewer.getSession().setValue( + viewer.update( i18n.translate('console.historyPage.noHistoryTextMessage', { defaultMessage: 'No history available', - }) + }), + inputMode ); } } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/console_history/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/console_history/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/editor.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/index.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts similarity index 56% rename from src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts index fa88e42fa557f..f9cfadf7fe34b 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/__tests__/utils.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/apply_editor_settings.ts @@ -17,20 +17,21 @@ * under the License. */ -import expect from '@kbn/expect'; +import { DevToolsSettings } from '../../../../../services'; +import { CoreEditor } from '../../../../../types'; +import { CustomAceEditor } from '../../../../models/legacy_core_editor'; -import utils from '../utils'; - -describe('console utils', () => { - describe('collapseLiteralStrings', () => { - it('will collapse multiline strings', () => { - const multiline = '{ "foo": """bar\nbaz""" }'; - expect(utils.collapseLiteralStrings(multiline)).to.be('{ "foo": "bar\\nbaz" }'); - }); - - it('will collapse multiline strings with CRLF endings', () => { - const multiline = '{ "foo": """bar\r\nbaz""" }'; - expect(utils.collapseLiteralStrings(multiline)).to.be('{ "foo": "bar\\r\\nbaz" }'); +export function applyCurrentSettings( + editor: CoreEditor | CustomAceEditor, + settings: DevToolsSettings +) { + if ((editor as any).setStyles) { + (editor as CoreEditor).setStyles({ + wrapLines: settings.wrapMode, + fontSize: settings.fontSize + 'px', }); - }); -}); + } else { + (editor as CustomAceEditor).getSession().setUseWrapMode(settings.wrapMode); + (editor as CustomAceEditor).container.style.fontSize = settings.fontSize + 'px'; + } +} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx similarity index 75% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx index a9db5cfcac3eb..5df72c0f03496 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.mock.tsx @@ -21,24 +21,27 @@ jest.mock('../../../../contexts/editor_context/editor_registry.ts', () => ({ instance: { setInputEditor: () => {}, getInputEditor: () => ({ - getRequestsInRange: (cb: any) => cb([{ test: 'test' }]), + getRequestsInRange: async () => [{ test: 'test' }], }), }, })); jest.mock('../../../../components/editor_example.tsx', () => {}); -jest.mock('../../../../../../../public/quarantined/src/mappings.js', () => ({ +jest.mock('../../../../../lib/mappings/mappings', () => ({ retrieveAutoCompleteInfo: () => {}, clearSubscriptions: () => {}, })); -jest.mock('../../../../../../../public/quarantined/src/input.ts', () => { +jest.mock('../../../../models/sense_editor', () => { return { - initializeEditor: () => ({ - $el: { - css: () => {}, - }, - focus: () => {}, - update: () => {}, - getSession: () => ({ on: () => {}, setUseWrapMode: () => {} }), + create: () => ({ + getCoreEditor: () => ({ + registerKeyboardShortcut: jest.fn(), + setStyles: jest.fn(), + getContainer: () => ({ + focus: () => {}, + }), + on: jest.fn(), + }), + update: jest.fn(), commands: { addCommand: () => {}, }, diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx similarity index 78% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index b99249b2b0016..442ed330e9b7a 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -21,8 +21,6 @@ import React, { CSSProperties, useCallback, useEffect, useRef, useState } from ' import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import $ from 'jquery'; - import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useServicesContext, useEditorReadContext } from '../../../../contexts'; import { useUIAceKeyboardMode } from '../use_ui_ace_keyboard_mode'; @@ -34,17 +32,11 @@ import { applyCurrentSettings } from './apply_editor_settings'; import { useSendCurrentRequestToES, useSetInputEditor } from '../../../../hooks'; +import * as senseEditor from '../../../../models/sense_editor'; // @ts-ignore -import { initializeEditor } from '../../../../../../../public/quarantined/src/input'; -// @ts-ignore -import mappings from '../../../../../../../public/quarantined/src/mappings'; +import mappings from '../../../../../lib/mappings/mappings'; import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; -import { loadRemoteState } from './load_remote_editor_state'; - -export interface EditorProps { - previousStateLocation?: 'stored' | string; -} const abs: CSSProperties = { position: 'absolute', @@ -61,10 +53,11 @@ const DEFAULT_INPUT_VALUE = `GET _search } }`; -function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { +function EditorUI() { const { services: { history, notifications }, docLinkVersion, + elasticsearchUrl, } = useServicesContext(); const { settings } = useEditorReadContext(); @@ -72,8 +65,7 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { const sendCurrentRequestToES = useSendCurrentRequestToES(); const editorRef = useRef(null); - const actionsRef = useRef(null); - const editorInstanceRef = useRef(null); + const editorInstanceRef = useRef(null); const [textArea, setTextArea] = useState(null); useUIAceKeyboardMode(textArea); @@ -87,24 +79,18 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { }, [docLinkVersion]); useEffect(() => { - const $editor = $(editorRef.current!); - const $actions = $(actionsRef.current!); - editorInstanceRef.current = initializeEditor($editor, $actions); - - if (previousStateLocation === 'stored') { - const { content } = history.getSavedEditorState() || { - content: DEFAULT_INPUT_VALUE, - }; - editorInstanceRef.current.update(content); - } else { - loadRemoteState({ url: previousStateLocation, input: editorInstanceRef.current }); - } + editorInstanceRef.current = senseEditor.create(editorRef.current!); + + const { content: text } = history.getSavedEditorState() || { + content: DEFAULT_INPUT_VALUE, + }; + editorInstanceRef.current.update(text); function setupAutosave() { let timer: number; const saveDelay = 500; - editorInstanceRef.current.getSession().on('change', function onChange() { + editorInstanceRef.current!.getCoreEditor().on('change', () => { if (timer) { clearTimeout(timer); } @@ -114,7 +100,7 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { function saveCurrentState() { try { - const content = editorInstanceRef.current.getValue(); + const content = editorInstanceRef.current!.getCoreEditor().getValue(); history.updateCurrentState(content); } catch (e) { // Ignoring saving error @@ -128,7 +114,7 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { const unsubscribeResizer = subscribeResizeChecker( editorRef.current!, - editorInstanceRef.current + editorInstanceRef.current.getCoreEditor() ); setupAutosave(); @@ -136,17 +122,20 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) { unsubscribeResizer(); mappings.clearSubscriptions(); }; - }, [history, previousStateLocation, setInputEditor]); + }, [history, setInputEditor]); useEffect(() => { - applyCurrentSettings(editorInstanceRef.current!, settings); + applyCurrentSettings(editorInstanceRef.current!.getCoreEditor(), settings); // Preserve legacy focus behavior after settings have updated. - editorInstanceRef.current!.focus(); + editorInstanceRef + .current!.getCoreEditor() + .getContainer() + .focus(); }, [settings]); useEffect(() => { registerCommands({ - input: editorInstanceRef.current, + senseEditor: editorInstanceRef.current!, sendCurrentRequestToES, openDocumentation, }); @@ -157,7 +146,6 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) {
    { - editorInstanceRef.current!.getRequestsAsCURL(cb); + getCurl={() => { + return editorInstanceRef.current!.getRequestsAsCURL(elasticsearchUrl); }} getDocumentation={() => { return getDocumentation(editorInstanceRef.current!, docLinkVersion); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx similarity index 78% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx index 3690ea61d5684..b1398d0a19a8f 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -18,18 +18,14 @@ */ import React, { useEffect, useRef } from 'react'; -import $ from 'jquery'; - -// @ts-ignore -import { initializeOutput } from '../../../../../../../public/quarantined/src/output'; +import { createReadOnlyAceEditor, CustomAceEditor } from '../../../../models/legacy_core_editor'; import { useServicesContext, useEditorReadContext, useRequestReadContext, } from '../../../../contexts'; -// @ts-ignore -import utils from '../../../../../../../public/quarantined/src/utils'; +import * as utils from '../../../../../lib/utils/utils'; import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; import { applyCurrentSettings } from './apply_editor_settings'; @@ -45,7 +41,7 @@ function modeForContentType(contentType: string) { function EditorOutputUI() { const editorRef = useRef(null); - const editorInstanceRef = useRef(null); + const editorInstanceRef = useRef(null); const { services } = useServicesContext(); const { settings: readOnlySettings } = useEditorReadContext(); @@ -54,8 +50,7 @@ function EditorOutputUI() { } = useRequestReadContext(); useEffect(() => { - const editor$ = $(editorRef.current!); - editorInstanceRef.current = initializeOutput(editor$, services.settings); + editorInstanceRef.current = createReadOnlyAceEditor(editorRef.current!); const unsubscribe = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current); return () => { @@ -64,25 +59,26 @@ function EditorOutputUI() { }, [services.settings]); useEffect(() => { + const editor = editorInstanceRef.current!; if (data) { const mode = modeForContentType(data[0].response.contentType); - editorInstanceRef.current.session.setMode(mode); - editorInstanceRef.current.update( + editor.session.setMode(mode); + editor.update( data - .map(d => d.response.value) + .map(d => d.response.value as string) .map(readOnlySettings.tripleQuotes ? utils.expandLiteralStrings : a => a) .join('\n') ); } else if (error) { - editorInstanceRef.current.session.setMode(modeForContentType(error.contentType)); - editorInstanceRef.current.update(error.value); + editor.session.setMode(modeForContentType(error.contentType)); + editor.update(error.value); } else { - editorInstanceRef.current.update(''); + editor.update(''); } }, [readOnlySettings, data, error]); useEffect(() => { - applyCurrentSettings(editorInstanceRef.current, readOnlySettings); + applyCurrentSettings(editorInstanceRef.current!, readOnlySettings); }, [readOnlySettings]); return ( diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts similarity index 55% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts index d1605c1aefa56..39621a9cb3dd2 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/keyboard_shortcuts.ts @@ -17,49 +17,60 @@ * under the License. */ import { throttle } from 'lodash'; +import { SenseEditor } from '../../../../models/sense_editor'; interface Actions { - input: any; // TODO: Wrap this in an editor interface + senseEditor: SenseEditor; sendCurrentRequestToES: () => void; openDocumentation: () => void; } -export function registerCommands({ input, sendCurrentRequestToES, openDocumentation }: Actions) { - const throttledAutoIndent = throttle(() => input.autoIndent(), 500, { +export function registerCommands({ + senseEditor, + sendCurrentRequestToES, + openDocumentation, +}: Actions) { + const throttledAutoIndent = throttle(() => senseEditor.autoIndent(), 500, { leading: true, trailing: true, }); - input.commands.addCommand({ - name: 'send to elasticsearch', - bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' }, - exec: () => sendCurrentRequestToES(), + const coreEditor = senseEditor.getCoreEditor(); + + coreEditor.registerKeyboardShortcut({ + keys: { win: 'Ctrl-Enter', mac: 'Command-Enter' }, + name: 'send to Elasticsearch', + fn: () => sendCurrentRequestToES(), }); - input.commands.addCommand({ + + coreEditor.registerKeyboardShortcut({ name: 'open documentation', - bindKey: { win: 'Ctrl-/', mac: 'Command-/' }, - exec: () => { + keys: { win: 'Ctrl-/', mac: 'Command-/' }, + fn: () => { openDocumentation(); }, }); - input.commands.addCommand({ + + coreEditor.registerKeyboardShortcut({ name: 'auto indent request', - bindKey: { win: 'Ctrl-I', mac: 'Command-I' }, - exec: () => { + keys: { win: 'Ctrl-I', mac: 'Command-I' }, + fn: () => { throttledAutoIndent(); }, }); - input.commands.addCommand({ + + coreEditor.registerKeyboardShortcut({ name: 'move to previous request start or end', - bindKey: { win: 'Ctrl-Up', mac: 'Command-Up' }, - exec: () => { - input.moveToPreviousRequestEdge(); + keys: { win: 'Ctrl-Up', mac: 'Command-Up' }, + fn: () => { + senseEditor.moveToPreviousRequestEdge(); }, }); - input.commands.addCommand({ + + coreEditor.registerKeyboardShortcut({ name: 'move to next request start or end', - bindKey: { win: 'Ctrl-Down', mac: 'Command-Down' }, - exec: () => { - input.moveToNextRequestEdge(); + keys: { win: 'Ctrl-Down', mac: 'Command-Down' }, + fn: () => { + senseEditor.moveToNextRequestEdge(false); }, }); } diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts new file mode 100644 index 0000000000000..797ff5744eec3 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_menu_actions.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +// @ts-ignore +import { getEndpointFromPosition } from '../../../../lib/autocomplete/autocomplete'; +import { SenseEditor } from '../../../models/sense_editor'; + +export async function autoIndent(editor: SenseEditor, event: Event) { + event.preventDefault(); + await editor.autoIndent(); + editor + .getCoreEditor() + .getContainer() + .focus(); +} + +export function getDocumentation( + editor: SenseEditor, + docLinkVersion: string +): Promise { + return editor.getRequestsInRange().then(requests => { + if (!requests || requests.length === 0) { + return null; + } + const position = requests[0].range.end; + position.column = position.column - 1; + const endpoint = getEndpointFromPosition(editor, position, editor.parser); + if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) { + return endpoint.documentation + .replace('/master/', `/${docLinkVersion}/`) + .replace('/current/', `/${docLinkVersion}/`); + } else { + return null; + } + }); +} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/subscribe_console_resize_checker.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/subscribe_console_resize_checker.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/use_ui_ace_keyboard_mode.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/use_ui_ace_keyboard_mode.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/use_ui_ace_keyboard_mode.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/use_ui_ace_keyboard_mode.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/get_top_nav.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/get_top_nav.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/main/get_top_nav.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/main/get_top_nav.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/main/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/containers/main/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/settings.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/containers/settings.tsx index 8440faa6eeea8..795103a5c95bb 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/settings.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/settings.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { AutocompleteOptions, DevToolsSettingsModal } from '../components'; // @ts-ignore -import mappings from '../../../../public/quarantined/src/mappings'; +import mappings from '../../lib/mappings/mappings'; import { useServicesContext, useEditorActionContext } from '../contexts'; import { DevToolsSettings } from '../../services'; diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/create_use_context.ts b/src/legacy/core_plugins/console/public/np_ready/application/contexts/create_use_context.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/create_use_context.ts rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/create_use_context.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/editor_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/editor_context.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/editor_context.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/editor_context.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/editor_registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/editor_registry.ts similarity index 85% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/editor_registry.ts rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/editor_registry.ts index bdccc1af0860c..64b0cddb4189b 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/editor_registry.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/editor_registry.ts @@ -17,15 +17,17 @@ * under the License. */ +import { SenseEditor } from '../../models/sense_editor'; + export class EditorRegistry { - inputEditor: any; + inputEditor: SenseEditor | undefined; - setInputEditor(inputEditor: any) { + setInputEditor(inputEditor: SenseEditor) { this.inputEditor = inputEditor; } getInputEditor() { - return this.inputEditor; + return this.inputEditor!; } } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/editor_context/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/editor_context/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/request_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/request_context.tsx similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/request_context.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/request_context.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/application/contexts/services_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx similarity index 98% rename from src/legacy/core_plugins/console/np_ready/public/application/contexts/services_context.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx index f715b1ae53a78..77f0924a51842 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/contexts/services_context.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx @@ -28,6 +28,7 @@ interface ContextValue { settings: Settings; notifications: NotificationsSetup; }; + elasticsearchUrl: string; docLinkVersion: string; } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/factories/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/factories/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/factories/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/factories/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/factories/token_iterator.ts b/src/legacy/core_plugins/console/public/np_ready/application/factories/token_iterator.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/factories/token_iterator.ts rename to src/legacy/core_plugins/console/public/np_ready/application/factories/token_iterator.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/restore_request_from_history.ts similarity index 59% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/restore_request_from_history.ts index b053e605b5fae..24af2df69809f 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/restore_request_from_history.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/restore_request_from_history.ts @@ -17,26 +17,29 @@ * under the License. */ +import RowParser from '../../../lib/row_parser'; +import { SenseEditor } from '../../models/sense_editor'; /** * This function is considered legacy and should not be changed or updated before we have editor * interfaces in place (it's using a customized version of Ace directly). */ -export function restoreRequestFromHistory(input: any, req: any) { - const session = input.getSession(); - let pos = input.getCursorPosition(); +export function restoreRequestFromHistory(editor: SenseEditor, req: any) { + const coreEditor = editor.getCoreEditor(); + let pos = coreEditor.getCurrentPosition(); let prefix = ''; let suffix = '\n'; - if (input.parser.isStartRequestRow(pos.row)) { - pos.column = 0; + const parser = new RowParser(coreEditor); + if (parser.isStartRequestRow(pos.lineNumber)) { + pos.column = 1; suffix += '\n'; - } else if (input.parser.isEndRequestRow(pos.row)) { - const line = session.getLine(pos.row); - pos.column = line.length; + } else if (parser.isEndRequestRow(pos.lineNumber)) { + const line = coreEditor.getLineValue(pos.lineNumber); + pos.column = line.length + 1; prefix = '\n\n'; - } else if (input.parser.isInBetweenRequestsRow(pos.row)) { - pos.column = 0; + } else if (parser.isInBetweenRequestsRow(pos.lineNumber)) { + pos.column = 1; } else { - pos = input.nextRequestEnd(pos); + pos = editor.nextRequestEnd(pos); prefix = '\n\n'; } @@ -47,8 +50,8 @@ export function restoreRequestFromHistory(input: any, req: any) { s += suffix; - session.insert(pos, s); - input.clearSelection(); - input.moveCursorTo(pos.row + prefix.length, 0); - input.focus(); + coreEditor.insert(pos, s); + coreEditor.moveCursorToPosition({ lineNumber: pos.lineNumber + prefix.length, column: 1 }); + coreEditor.clearSelection(); + coreEditor.getContainer().focus(); } diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_restore_request_from_history/use_restore_request_from_history.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts similarity index 96% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts index 22fa4477e9012..11c1f6638e9cf 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/send_request_to_es.ts @@ -17,10 +17,9 @@ * under the License. */ +import * as utils from '../../../lib/utils/utils'; // @ts-ignore -import utils from '../../../../../public/quarantined/src/utils'; -// @ts-ignore -import * as es from '../../../../../public/quarantined/src/es'; +import * as es from '../../../lib/es/es'; import { BaseResponseType } from '../../../types/common'; export interface EsRequestArgs { diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts similarity index 94% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts index 52661103b2d13..b51c29f8e9db6 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts @@ -23,7 +23,7 @@ import { useServicesContext } from '../../contexts'; import { sendRequestToES } from './send_request_to_es'; import { useRequestActionContext } from '../../contexts'; // @ts-ignore -import mappings from '../../../../../public/quarantined/src/mappings'; +import mappings from '../../../lib/mappings/mappings'; export const useSendCurrentRequestToES = () => { const { @@ -36,7 +36,7 @@ export const useSendCurrentRequestToES = () => { dispatch({ type: 'sendRequest', payload: undefined }); try { const editor = registry.getInputEditor(); - const requests = await new Promise(resolve => editor.getRequestsInRange(resolve)); + const requests = await editor.getRequestsInRange(); if (!requests.length) { dispatch({ type: 'requestFail', diff --git a/src/legacy/core_plugins/console/np_ready/public/application/hooks/use_set_input_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/hooks/use_set_input_editor.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx similarity index 91% rename from src/legacy/core_plugins/console/np_ready/public/application/index.tsx rename to src/legacy/core_plugins/console/public/np_ready/application/index.tsx index e181caf23d2cb..239e4320f00f8 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; -import { NotificationsSetup } from 'src/core/public'; +import { NotificationsSetup } from 'kibana/public'; import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; import { Main } from './containers'; import { createStorage, createHistory, createSettings, Settings } from '../services'; @@ -32,8 +32,9 @@ export function boot(deps: { docLinkVersion: string; I18nContext: any; notifications: NotificationsSetup; + elasticsearchUrl: string; }) { - const { I18nContext, notifications, docLinkVersion } = deps; + const { I18nContext, notifications, docLinkVersion, elasticsearchUrl } = deps; const storage = createStorage({ engine: window.localStorage, @@ -47,6 +48,7 @@ export function boot(deps: { { - let input; + let coreEditor; beforeEach(() => { // Set up our document body document.body.innerHTML = @@ -36,33 +33,25 @@ describe('Input Tokenization', () => {
    `; - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), - ); - - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), - ); - input.$el.show(); - input.autocomplete._test.removeChangeListener(); + coreEditor = create(document.querySelector('#ConAppEditor')); + + $(coreEditor.getContainer()).show(); }); afterEach(() => { - input.$el.hide(); - input.autocomplete._test.addChangeListener(); + $(coreEditor.getContainer()).hide(); }); function tokensAsList() { - const iter = new tokenIterator.TokenIterator(input.getSession(), 0, 0); + const iter = createTokenIterator({ editor: coreEditor, position: { lineNumber: 1, column: 1 } }); const ret = []; let t = iter.getCurrentToken(); - if (input.parser.isEmptyToken(t)) { - t = input.parser.nextNonEmptyToken(iter); + const parser = new RowParser(coreEditor); + if (parser.isEmptyToken(t)) { + t = parser.nextNonEmptyToken(iter); } while (t) { ret.push({ value: t.value, type: t.type }); - t = input.parser.nextNonEmptyToken(iter); + t = parser.nextNonEmptyToken(iter); } return ret; @@ -83,18 +72,15 @@ describe('Input Tokenization', () => { data = prefix; } - test('Token test ' + testCount++ + ' prefix: ' + prefix, async function (done) { - input.update(data, function () { - const tokens = tokensAsList(); - const normTokenList = []; - for (let i = 0; i < tokenList.length; i++) { - normTokenList.push({ type: tokenList[i++], value: tokenList[i] }); - } - - expect(tokens).toEqual(normTokenList); - done(); - }); + test('Token test ' + testCount++ + ' prefix: ' + prefix, async function () { + await coreEditor.setValue(data, true); + const tokens = tokensAsList(); + const normTokenList = []; + for (let i = 0; i < tokenList.length; i++) { + normTokenList.push({ type: tokenList[i++], value: tokenList[i] }); + } + expect(tokens).toEqual(normTokenList); }); } @@ -271,10 +257,8 @@ describe('Input Tokenization', () => { function statesAsList() { const ret = []; - const session = input.getSession(); - const maxLine = session.getLength(); - for (let row = 0; row < maxLine; row++) ret.push(session.getState(row)); - + const maxLine = coreEditor.getLineCount(); + for (let line = 1; line <= maxLine; line++) ret.push(coreEditor.getLineState(line)); return ret; } @@ -292,12 +276,10 @@ describe('Input Tokenization', () => { data = prefix; } - test('States test ' + testCount++ + ' prefix: ' + prefix, async function (done) { - input.update(data, function () { - const modes = statesAsList(); - expect(modes).toEqual(statesList); - done(); - }); + test('States test ' + testCount++ + ' prefix: ' + prefix, async function () { + await coreEditor.setValue(data, true); + const modes = statesAsList(); + expect(modes).toEqual(statesList); }); } diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js similarity index 89% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js index 36c081f5ef923..b860f691dadb2 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/output_tokenization.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/__tests__/output_tokenization.test.js @@ -16,26 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import ace from 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; - const $ = require('jquery'); -const RowParser = require('../../src/sense_editor/row_parser'); - -import { initializeOutput } from '../../src/output'; +import RowParser from '../../../../lib/row_parser'; +import ace from 'brace'; +import { createReadOnlyAceEditor } from '../create_readonly'; let output; - const tokenIterator = ace.acequire('ace/token_iterator'); describe('Output Tokenization', () => { beforeEach(() => { - output = initializeOutput($('#ConAppOutput')); - output.$el.show(); + output = createReadOnlyAceEditor(document.querySelector('#ConAppOutput')); + $(output.container).show(); }); + afterEach(() => { - output.$el.hide(); + $(output.container).hide(); }); function tokensAsList() { diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts new file mode 100644 index 0000000000000..8e03182621c83 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create.ts @@ -0,0 +1,30 @@ +/* + * 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 ace from 'brace'; +import { LegacyCoreEditor } from './legacy_core_editor'; + +export const create = (el: HTMLElement) => { + const actions = document.querySelector('#ConAppEditorActions'); + if (!actions) { + throw new Error('Could not find ConAppEditorActions element!'); + } + const aceEditor = ace.edit(el); + return new LegacyCoreEditor(aceEditor, actions); +}; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/output.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.ts similarity index 70% rename from src/legacy/core_plugins/console/public/quarantined/src/output.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.ts index 42555f17f19c1..ce8ededd0b12c 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/output.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/create_readonly.ts @@ -18,19 +18,28 @@ */ import _ from 'lodash'; -const ace = require('brace'); -const OutputMode = require('./sense_editor/mode/output'); -const smartResize = require('./smart_resize'); +import ace from 'brace'; +// @ts-ignore +import * as OutputMode from './mode/output'; +import smartResize from './smart_resize'; -let output; -export function initializeOutput($el) { - output = ace.acequire('ace/ace').edit($el[0]); +export interface CustomAceEditor extends ace.Editor { + update: (text: string, mode?: any, cb?: () => void) => void; + append: (text: string, foldPrevious?: boolean, cb?: () => void) => void; +} + +/** + * Note: using read-only ace editor leaks the Ace editor API - use this as sparingly as possible or + * create an interface for it so that we don't rely directly on vendor APIs. + */ +export function createReadOnlyAceEditor(element: HTMLElement): CustomAceEditor { + const output: CustomAceEditor = ace.acequire('ace/ace').edit(element); const outputMode = new OutputMode.Mode(); output.$blockScrolling = Infinity; output.resize = smartResize(output); - output.update = (val, mode, cb) => { + output.update = (val: string, mode?: any, cb?: () => void) => { if (typeof mode === 'function') { cb = mode; mode = void 0; @@ -45,7 +54,7 @@ export function initializeOutput($el) { } }; - output.append = (val, foldPrevious, cb) => { + output.append = (val: string, foldPrevious?: boolean, cb?: () => void) => { if (typeof foldPrevious === 'function') { cb = foldPrevious; foldPrevious = true; @@ -65,12 +74,10 @@ export function initializeOutput($el) { } }; - output.$el = $el; - // eslint-disable-next-line (function setupSession(session) { session.setMode('ace/mode/text'); - session.setFoldStyle('markbeginend'); + (session as any).setFoldStyle('markbeginend'); session.setTabSize(2); session.setUseWrapMode(true); })(output.getSession()); @@ -80,7 +87,3 @@ export function initializeOutput($el) { return output; } - -export default function getOutput() { - return output; -} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.ts similarity index 75% rename from src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.ts index 8445863b6dfc3..7655bbcd106d2 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/apply_editor_settings.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/index.ts @@ -17,9 +17,12 @@ * under the License. */ -import { DevToolsSettings } from '../../../../../services'; +import 'brace'; +import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; +import 'brace/mode/json'; +import 'brace/mode/text'; -export function applyCurrentSettings(editor: any, settings: DevToolsSettings) { - editor.getSession().setUseWrapMode(settings.wrapMode); - editor.$el.css('font-size', settings.fontSize + 'px'); -} +export * from './legacy_core_editor'; +export * from './create_readonly'; +export * from './create'; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts similarity index 69% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts index 203d8a20b1055..66673ff42e4e8 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/setup_mocks.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.test.mocks.ts @@ -16,23 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -/* eslint no-undef: 0 */ -jest.mock('../../src/sense_editor/mode/worker', () => { - return { workerModule: { id: 'sense_editor/mode/worker', src: '' } }; -}); -jest.mock('ui/chrome', () => { - return { - getInjected() { - return 'http://localhost:9200'; - } - }; +jest.mock('./mode/worker', () => { + return { workerModule: { id: 'sense_editor/mode/worker', src: '' } }; }); -window.Worker = function () { +// @ts-ignore +window.Worker = function() { this.postMessage = () => {}; - this.terminate = () => {}; + (this as any).terminate = () => {}; }; + +// @ts-ignore window.URL = { createObjectURL: () => { return ''; @@ -45,14 +40,4 @@ import 'brace/ext/searchbox'; import 'brace/mode/json'; import 'brace/mode/text'; -jest.mock('../../../../np_ready/public/application', () => ({ legacyBackDoorToSettings: () => {}, })); - document.queryCommandSupported = () => true; - -import jQuery from 'jquery'; -jest.spyOn(jQuery, 'ajax').mockImplementation( - () => - new Promise(() => { - // never resolve - }) -); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts new file mode 100644 index 0000000000000..621f4eeb0163e --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts @@ -0,0 +1,340 @@ +/* + * 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 ace from 'brace'; +import { Editor as IAceEditor } from 'brace'; +import $ from 'jquery'; +import { CoreEditor, Position, Range, Token, TokensProvider, EditorEvent } from '../../../types'; +import { AceTokensProvider } from '../../../lib/ace_token_provider'; +import * as curl from '../sense_editor/curl'; +import smartResize from './smart_resize'; + +// @ts-ignore +import * as InputMode from './mode/input'; + +const _AceRange = ace.acequire('ace/range').Range; + +const rangeToAceRange = ({ start, end }: Range) => + new _AceRange(start.lineNumber - 1, start.column - 1, end.lineNumber - 1, end.column - 1); + +export class LegacyCoreEditor implements CoreEditor { + private _aceOnPaste: any; + $actions: any; + resize: () => void; + + constructor(private readonly editor: IAceEditor, actions: HTMLElement) { + this.$actions = $(actions); + this.editor.setShowPrintMargin(false); + + const session = this.editor.getSession(); + session.setMode(new InputMode.Mode()); + (session as any).setFoldStyle('markbeginend'); + session.setTabSize(2); + session.setUseWrapMode(true); + + this.resize = smartResize(this.editor); + + // Intercept ace on paste handler. + this._aceOnPaste = this.editor.onPaste; + this.editor.onPaste = this.DO_NOT_USE_onPaste.bind(this); + + this.editor.setOptions({ + enableBasicAutocompletion: true, + }); + + this.editor.$blockScrolling = Infinity; + this.hideActionsBar(); + this.editor.focus(); + } + + // dirty check for tokenizer state, uses a lot less cycles + // than listening for tokenizerUpdate + waitForLatestTokens(): Promise { + return new Promise(resolve => { + const session = this.editor.getSession(); + const checkInterval = 25; + + const check = () => { + // If the bgTokenizer doesn't exist, we can assume that the underlying editor has been + // torn down, e.g. by closing the History tab, and we don't need to do anything further. + if (session.bgTokenizer) { + // Wait until the bgTokenizer is done running before executing the callback. + if ((session.bgTokenizer as any).running) { + setTimeout(check, checkInterval); + } else { + resolve(); + } + } + }; + + setTimeout(check, 0); + }); + } + + getLineState(lineNumber: number) { + const session = this.editor.getSession(); + return session.getState(lineNumber - 1); + } + + getValueInRange(range: Range): string { + return this.editor.getSession().getTextRange(rangeToAceRange(range)); + } + + getTokenProvider(): TokensProvider { + return new AceTokensProvider(this.editor.getSession()); + } + + getValue(): string { + return this.editor.getValue(); + } + + setValue(text: string, forceRetokenize: boolean): Promise { + const session = this.editor.getSession(); + session.setValue(text); + return new Promise(resolve => { + if (!forceRetokenize) { + // resolve immediately + resolve(); + return; + } + + // force update of tokens, but not on this thread to allow for ace rendering. + setTimeout(function() { + let i; + for (i = 0; i < session.getLength(); i++) { + session.getTokens(i); + } + resolve(); + }); + }); + } + + getLineValue(lineNumber: number): string { + const session = this.editor.getSession(); + return session.getLine(lineNumber - 1); + } + + getCurrentPosition(): Position { + const cursorPosition = this.editor.getCursorPosition(); + return { + lineNumber: cursorPosition.row + 1, + column: cursorPosition.column + 1, + }; + } + + clearSelection(): void { + this.editor.clearSelection(); + } + + getTokenAt(pos: Position): Token | null { + const provider = this.getTokenProvider(); + return provider.getTokenAt(pos); + } + + insert(valueOrPos: string | Position, value?: string): void { + if (typeof valueOrPos === 'string') { + this.editor.insert(valueOrPos); + return; + } + const document = this.editor.getSession().getDocument(); + document.insert( + { + column: valueOrPos.column - 1, + row: valueOrPos.lineNumber - 1, + }, + value || '' + ); + } + + moveCursorToPosition(pos: Position): void { + this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 }); + } + + replace(range: Range, value: string): void { + const session = this.editor.getSession(); + session.replace(rangeToAceRange(range), value); + } + + getLines(startLine: number, endLine: number): string[] { + const session = this.editor.getSession(); + return session.getLines(startLine - 1, endLine - 1); + } + + replaceRange(range: Range, value: string) { + const pos = this.editor.getCursorPosition(); + this.editor.getSession().replace(rangeToAceRange(range), value); + + const maxRow = Math.max(range.start.lineNumber - 1 + value.split('\n').length - 1, 1); + pos.row = Math.min(pos.row, maxRow); + this.editor.moveCursorToPosition(pos); + // ACE UPGRADE - check if needed - at the moment the above may trigger a selection. + this.editor.clearSelection(); + } + + getSelectionRange() { + const result = this.editor.getSelectionRange(); + return { + start: { + lineNumber: result.start.row + 1, + column: result.start.column + 1, + }, + end: { + lineNumber: result.end.row + 1, + column: result.end.column + 1, + }, + }; + } + + getLineCount() { + const text = this.getValue(); + return text.split('\n').length; + } + + addMarker(range: Range) { + return this.editor + .getSession() + .addMarker(rangeToAceRange(range), 'ace_snippet-marker', 'fullLine', false); + } + + removeMarker(ref: any) { + this.editor.getSession().removeMarker(ref); + } + + getWrapLimit(): number { + return this.editor.getSession().getWrapLimit(); + } + + on(event: EditorEvent, listener: () => void) { + if (event === 'changeCursor') { + this.editor.getSession().selection.on(event, listener); + } else if (event === 'changeSelection') { + this.editor.on(event, listener); + } else { + this.editor.getSession().on(event, listener); + } + } + + off(event: EditorEvent, listener: () => void) { + if (event === 'changeSelection') { + this.editor.off(event, listener); + } + } + + isCompleterActive() { + // Secrets of the arcane here. + return Boolean((this.editor as any).completer && (this.editor as any).completer.activated); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + private DO_NOT_USE_onPaste(text: string) { + if (text && curl.detectCURL(text)) { + const curlInput = curl.parseCURL(text); + this.editor.insert(curlInput); + return; + } + this._aceOnPaste.call(this.editor, text); + } + + private setActionsBar = (top?: any) => { + if (top === null) { + this.$actions.css('visibility', 'hidden'); + } else { + this.$actions.css({ + top, + visibility: 'visible', + }); + } + }; + + private hideActionsBar = () => { + this.setActionsBar(); + }; + + execCommand(cmd: string) { + this.editor.execCommand(cmd); + } + + getContainer(): HTMLDivElement { + return this.editor.container as HTMLDivElement; + } + + setStyles(styles: { wrapLines: boolean; fontSize: string }) { + this.editor.getSession().setUseWrapMode(styles.wrapLines); + this.editor.container.style.fontSize = styles.fontSize; + } + + registerKeyboardShortcut(opts: { keys: string; fn: () => void; name: string }): void { + this.editor.commands.addCommand({ + exec: opts.fn, + name: opts.name, + bindKey: opts.keys, + }); + } + + legacyUpdateUI(range: any) { + if (!this.$actions) { + return; + } + if (range) { + // elements are positioned relative to the editor's container + // pageY is relative to page, so subtract the offset + // from pageY to get the new top value + const offsetFromPage = $(this.editor.container).offset()!.top; + const startRow = range.start.lineNumber - 1; + const startColumn = range.start.column; + const firstLine = this.getLineValue(startRow); + const maxLineLength = this.getWrapLimit() - 5; + const isWrapping = firstLine.length > maxLineLength; + const getScreenCoords = (row: number) => + this.editor.renderer.textToScreenCoordinates(row, startColumn).pageY - offsetFromPage; + const topOfReq = getScreenCoords(startRow); + + if (topOfReq >= 0) { + let offset = 0; + if (isWrapping) { + // Try get the line height of the text area in pixels. + const textArea = $(this.editor.container.querySelector('textArea')!); + const hasRoomOnNextLine = this.getLineValue(startRow + 1).length < maxLineLength; + if (textArea && hasRoomOnNextLine) { + // Line height + the number of wraps we have on a line. + offset += this.getLineValue(startRow).length * textArea.height()!; + } else { + if (startRow > 0) { + this.setActionsBar(getScreenCoords(startRow - 1)); + return; + } + this.setActionsBar(getScreenCoords(startRow + 1)); + return; + } + } + this.setActionsBar(topOfReq + offset); + return; + } + + const bottomOfReq = + this.editor.renderer.textToScreenCoordinates(range.end.lineNumber, range.end.column).pageY - + offsetFromPage; + + if (bottomOfReq >= 0) { + this.setActionsBar(0); + return; + } + } + } +} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/input_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/output_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/output_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/script_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/script_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/index.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/index.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/index.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/index.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/worker.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/worker.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/worker/worker.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/worker/worker.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/x_json_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/x_json_highlight_rules.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/mode/x_json_highlight_rules.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/x_json_highlight_rules.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/smart_resize.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.ts similarity index 92% rename from src/legacy/core_plugins/console/public/quarantined/src/smart_resize.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.ts index 8ac5eda345c23..b88e0e44591d8 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/smart_resize.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/smart_resize.ts @@ -19,7 +19,8 @@ import { get, throttle } from 'lodash'; -export default function (editor) { +// eslint-disable-next-line import/no-default-export +export default function(editor: any) { const resize = editor.resize; const throttledResize = throttle(() => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/theme_sense_dark.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/sense_editor/theme_sense_dark.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/theme_sense_dark.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor_input1.txt b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/editor_input1.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/editor_input1.txt rename to src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/editor_input1.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js similarity index 80% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js index 9df4aa5e0bece..0950992f1c0fe 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/integration.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/integration.test.js @@ -16,51 +16,44 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/json'; -import { initializeEditor } from '../../src/input'; +import '../sense_editor.test.mocks'; +import { create } from '../create'; import _ from 'lodash'; const $ = require('jquery'); -const kb = require('../../src/kb'); -const mappings = require('../../src/mappings'); - - +const kb = require('../../../../lib/kb/kb'); +const mappings = require('../../../../lib/mappings/mappings'); describe('Integration', () => { - let input; + let senseEditor; beforeEach(() => { // Set up our document body document.body.innerHTML = '
    '; - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions') - ); - input.$el.show(); - input.autocomplete._test.removeChangeListener(); + senseEditor = create(document.querySelector('#ConAppEditor')); + $(senseEditor.getCoreEditor().getContainer()).show(); + senseEditor.autocomplete._test.removeChangeListener(); }); afterEach(() => { - input.$el.hide(); - input.autocomplete._test.addChangeListener(); + $(senseEditor.getCoreEditor().getContainer()).hide(); + senseEditor.autocomplete._test.addChangeListener(); }); function processContextTest(data, mapping, kbSchemes, requestLine, testToRun) { test(testToRun.name, async function (done) { - let rowOffset = 0; // add one for the extra method line + let lineOffset = 0; // add one for the extra method line let editorValue = data; if (requestLine != null) { if (data != null) { editorValue = requestLine + '\n' + data; - rowOffset = 1; + lineOffset = 1; } else { editorValue = requestLine; } } - testToRun.cursor.row += rowOffset; + testToRun.cursor.lineNumber += lineOffset; mappings.clear(); mappings.loadMappings(mapping); @@ -81,109 +74,104 @@ describe('Integration', () => { } kb.setActiveApi(testApi); const { cursor } = testToRun; - const { row, column } = cursor; - input.update(editorValue, function () { - input.moveCursorTo(row, column); + await senseEditor.update(editorValue, true); + senseEditor.getCoreEditor().moveCursorToPosition(cursor); - // allow ace rendering to move cursor so it will be seen during test - handy for debugging. - //setTimeout(function () { - input.completer = { - base: {}, - changeListener: function () {}, - }; // mimic auto complete + // allow ace rendering to move cursor so it will be seen during test - handy for debugging. + //setTimeout(function () { + senseEditor.completer = { + base: {}, + changeListener: function () {}, + }; // mimic auto complete - input.autocomplete._test.getCompletions( - input, - input.getSession(), - cursor, - '', - function (err, terms) { - if (testToRun.assertThrows) { - done(); - return; - } + senseEditor.autocomplete._test.getCompletions( + senseEditor, + null, + { row: cursor.lineNumber - 1, column: cursor.column - 1 }, + '', + function (err, terms) { + if (testToRun.assertThrows) { + done(); + return; + } - if (err) { - done(); - throw err; - } + if (err) { + throw err; + } - if (testToRun.no_context) { - expect(!terms || terms.length === 0).toBeTruthy(); - } else { - expect(terms).not.toBeNull(); - expect(terms.length).toBeGreaterThan(0); - } + if (testToRun.no_context) { + expect(!terms || terms.length === 0).toBeTruthy(); + } else { + expect(terms).not.toBeNull(); + expect(terms.length).toBeGreaterThan(0); + } - if (!terms || terms.length === 0) { - done(); - return; - } + if (!terms || terms.length === 0) { + done(); + return; + } - if (testToRun.autoCompleteSet) { - const expectedTerms = _.map(testToRun.autoCompleteSet, function (t) { - if (typeof t !== 'object') { - t = { name: t }; - } - return t; - }); - if (terms.length !== expectedTerms.length) { - expect(_.pluck(terms, 'name')).toEqual(_.pluck(expectedTerms, 'name')); - } else { - const filteredActualTerms = _.map(terms, function ( - actualTerm, - i - ) { - const expectedTerm = expectedTerms[i]; - const filteredTerm = {}; - _.each(expectedTerm, function (v, p) { - filteredTerm[p] = actualTerm[p]; - }); - return filteredTerm; - }); - expect(filteredActualTerms).toEqual(expectedTerms); + if (testToRun.autoCompleteSet) { + const expectedTerms = _.map(testToRun.autoCompleteSet, function (t) { + if (typeof t !== 'object') { + t = { name: t }; } - done(); + return t; + }); + if (terms.length !== expectedTerms.length) { + expect(_.pluck(terms, 'name')).toEqual(_.pluck(expectedTerms, 'name')); + } else { + const filteredActualTerms = _.map(terms, function ( + actualTerm, + i + ) { + const expectedTerm = expectedTerms[i]; + const filteredTerm = {}; + _.each(expectedTerm, function (v, p) { + filteredTerm[p] = actualTerm[p]; + }); + return filteredTerm; + }); + expect(filteredActualTerms).toEqual(expectedTerms); } + } - const context = terms[0].context; - const { cursor: { row, column } } = testToRun; - input.autocomplete._test.addReplacementInfoToContext( - context, - { lineNumber: row + 1, column: column + 1 }, - terms[0].value - ); + const context = terms[0].context; + const { cursor: { lineNumber, column } } = testToRun; + senseEditor.autocomplete._test.addReplacementInfoToContext( + context, + { lineNumber, column }, + terms[0].value + ); - function ac(prop, propTest) { - if (typeof testToRun[prop] !== 'undefined') { - if (propTest) { - propTest(context[prop], testToRun[prop], prop); - } else { - expect(context[prop]).toEqual(testToRun[prop]); - } + function ac(prop, propTest) { + if (typeof testToRun[prop] !== 'undefined') { + if (propTest) { + propTest(context[prop], testToRun[prop], prop); + } else { + expect(context[prop]).toEqual(testToRun[prop]); } } + } - function posCompare(actual, expected) { - expect(actual.lineNumber).toEqual(expected.lineNumber + rowOffset); - expect(actual.column).toEqual(expected.column); - } - - function rangeCompare(actual, expected, name) { - posCompare(actual.start, expected.start, name + '.start'); - posCompare(actual.end, expected.end, name + '.end'); - } + function posCompare(actual, expected) { + expect(actual.lineNumber).toEqual(expected.lineNumber + lineOffset); + expect(actual.column).toEqual(expected.column); + } - ac('prefixToAdd'); - ac('suffixToAdd'); - ac('addTemplate'); - ac('textBoxPosition', posCompare); - ac('rangeToReplace', rangeCompare); - done(); + function rangeCompare(actual, expected, name) { + posCompare(actual.start, expected.start, name + '.start'); + posCompare(actual.end, expected.end, name + '.end'); } - ); - //}); - }); + + ac('prefixToAdd'); + ac('suffixToAdd'); + ac('addTemplate'); + ac('textBoxPosition', posCompare); + ac('rangeToReplace', rangeCompare); + done(); + } + ); }); } @@ -236,7 +224,7 @@ describe('Integration', () => { contextTests({}, MAPPING, SEARCH_KB, 'POST _search', [ { name: 'Empty doc', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -252,7 +240,7 @@ describe('Integration', () => { contextTests({}, MAPPING, SEARCH_KB, 'POST _no_context', [ { name: 'Missing KB', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, no_context: true, }, ]); @@ -276,7 +264,7 @@ describe('Integration', () => { [ { name: 'Missing KB - global auto complete', - cursor: { row: 2, column: 5 }, + cursor: { lineNumber: 3, column: 6 }, autoCompleteSet: ['t1'], }, ] @@ -296,7 +284,7 @@ describe('Integration', () => { [ { name: 'existing dictionary key, no template', - cursor: { row: 1, column: 6 }, + cursor: { lineNumber: 2, column: 6 }, initialValue: 'query', addTemplate: false, prefixToAdd: '', @@ -309,7 +297,7 @@ describe('Integration', () => { }, { name: 'existing inner dictionary key', - cursor: { row: 2, column: 7 }, + cursor: { lineNumber: 3, column: 8 }, initialValue: 'field', addTemplate: false, prefixToAdd: '', @@ -322,7 +310,7 @@ describe('Integration', () => { }, { name: 'existing dictionary key, yes template', - cursor: { row: 4, column: 7 }, + cursor: { lineNumber: 5, column: 8 }, initialValue: 'facets', addTemplate: true, prefixToAdd: '', @@ -335,13 +323,12 @@ describe('Integration', () => { }, { name: 'ignoring meta keys', - cursor: { row: 4, column: 14 }, + cursor: { lineNumber: 5, column: 15 }, no_context: true, }, ] ); - contextTests( '{\n' + ' "query": {\n' + @@ -356,7 +343,7 @@ describe('Integration', () => { [ { name: 'trailing comma, end of line', - cursor: { row: 4, column: 16 }, + cursor: { lineNumber: 5, column: 17 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -369,7 +356,7 @@ describe('Integration', () => { }, { name: 'trailing comma, beginning of line', - cursor: { row: 5, column: 1 }, + cursor: { lineNumber: 6, column: 2 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -382,7 +369,7 @@ describe('Integration', () => { }, { name: 'prefix comma, beginning of line', - cursor: { row: 6, column: 0 }, + cursor: { lineNumber: 7, column: 1 }, initialValue: '', addTemplate: true, prefixToAdd: ', ', @@ -395,7 +382,7 @@ describe('Integration', () => { }, { name: 'prefix comma, end of line', - cursor: { row: 5, column: 14 }, + cursor: { lineNumber: 6, column: 15 }, initialValue: '', addTemplate: true, prefixToAdd: ', ', @@ -437,31 +424,31 @@ describe('Integration', () => { [ { name: 'not matching object when { is not opened', - cursor: { row: 1, column: 12 }, + cursor: { lineNumber: 2, column: 13 }, initialValue: '', autoCompleteSet: ['{'], }, { name: 'not matching array when [ is not opened', - cursor: { row: 2, column: 12 }, + cursor: { lineNumber: 3, column: 13 }, initialValue: '', autoCompleteSet: ['['], }, { name: 'matching value with one_of', - cursor: { row: 3, column: 19 }, + cursor: { lineNumber: 4, column: 20 }, initialValue: '', autoCompleteSet: [1, 2], }, { name: 'matching value', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, initialValue: '', autoCompleteSet: [3], }, { name: 'matching any value with one_of', - cursor: { row: 5, column: 21 }, + cursor: { lineNumber: 6, column: 22 }, initialValue: '', autoCompleteSet: [4, 5], }, @@ -484,7 +471,7 @@ describe('Integration', () => { [ { name: '* matching everything', - cursor: { row: 5, column: 15 }, + cursor: { lineNumber: 6, column: 16 }, initialValue: '', addTemplate: true, prefixToAdd: '', @@ -517,7 +504,7 @@ describe('Integration', () => { [ { name: '{index} matching', - cursor: { row: 1, column: 15 }, + cursor: { lineNumber: 2, column: 16 }, autoCompleteSet: [ { name: 'index1', meta: 'index' }, { name: 'index2', meta: 'index' }, @@ -558,7 +545,7 @@ describe('Integration', () => { [ { name: 'Templates 1', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: [ tt('array', []), tt('fixed', { a: 1 }), @@ -569,7 +556,7 @@ describe('Integration', () => { }, { name: 'Templates - one off', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, autoCompleteSet: [tt('o1'), tt('o2')], }, ] @@ -612,7 +599,7 @@ describe('Integration', () => { [ { name: 'Conditionals', - cursor: { row: 2, column: 15 }, + cursor: { lineNumber: 3, column: 16 }, autoCompleteSet: [tt('always', {}), tt('match', {})], }, ] @@ -655,7 +642,7 @@ describe('Integration', () => { [ { name: 'Any of - templates', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: [ tt('any_of_mixed', []), tt('any_of_numbers', [1, 2]), @@ -664,22 +651,22 @@ describe('Integration', () => { }, { name: 'Any of - numbers', - cursor: { row: 2, column: 2 }, + cursor: { lineNumber: 3, column: 3 }, autoCompleteSet: [1, 2, 3], }, { name: 'Any of - object', - cursor: { row: 6, column: 2 }, + cursor: { lineNumber: 7, column: 3 }, autoCompleteSet: [tt('a', 1), tt('b', 2), tt('c', 1)], }, { name: 'Any of - mixed - obj', - cursor: { row: 11, column: 2 }, + cursor: { lineNumber: 12, column: 3 }, autoCompleteSet: [tt('a', 1)], }, { name: 'Any of - mixed - both', - cursor: { row: 13, column: 2 }, + cursor: { lineNumber: 14, column: 3 }, autoCompleteSet: [tt('{'), tt(3)], }, ] @@ -702,7 +689,7 @@ describe('Integration', () => { [ { name: 'Empty string as default', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('query', '')], }, ] @@ -785,7 +772,7 @@ describe('Integration', () => { [ { name: 'Relative scope link test', - cursor: { row: 2, column: 12 }, + cursor: { lineNumber: 3, column: 13 }, autoCompleteSet: [ tt('b', {}), tt('c', {}), @@ -798,37 +785,37 @@ describe('Integration', () => { }, { name: 'External scope link test', - cursor: { row: 3, column: 12 }, + cursor: { lineNumber: 4, column: 13 }, autoCompleteSet: [tt('t2', 1)], }, { name: 'Global scope link test', - cursor: { row: 4, column: 12 }, + cursor: { lineNumber: 5, column: 13 }, autoCompleteSet: [tt('t1', 2), tt('t1a', {})], }, { name: 'Global scope link with an internal scope link', - cursor: { row: 5, column: 17 }, + cursor: { lineNumber: 6, column: 18 }, autoCompleteSet: [tt('t1', 2), tt('t1a', {})], }, { name: 'Entire endpoint scope link test', - cursor: { row: 7, column: 12 }, + cursor: { lineNumber: 8, column: 13 }, autoCompleteSet: [tt('target', {})], }, { name: 'A scope link within an array', - cursor: { row: 9, column: 10 }, + cursor: { lineNumber: 10, column: 11 }, autoCompleteSet: [tt('t2', 1)], }, { name: 'A function based scope link', - cursor: { row: 11, column: 12 }, + cursor: { lineNumber: 12, column: 13 }, autoCompleteSet: [tt('a', 1), tt('b', 2)], }, { name: 'A global scope link with wrong link', - cursor: { row: 12, column: 12 }, + cursor: { lineNumber: 13, column: 13 }, assertThrows: /broken/, }, ] @@ -857,7 +844,7 @@ describe('Integration', () => { [ { name: 'Top level scope link', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('t1', 2)], }, ] @@ -883,7 +870,7 @@ describe('Integration', () => { [ { name: 'Path after empty object', - cursor: { row: 1, column: 10 }, + cursor: { lineNumber: 2, column: 11 }, autoCompleteSet: ['a', 'b'], }, ] @@ -899,7 +886,7 @@ describe('Integration', () => { [ { name: 'Replace an empty string', - cursor: { row: 1, column: 4 }, + cursor: { lineNumber: 2, column: 5 }, rangeToReplace: { start: { lineNumber: 2, column: 4 }, end: { lineNumber: 2, column: 10 }, @@ -931,12 +918,12 @@ describe('Integration', () => { [ { name: 'List of objects - internal autocomplete', - cursor: { row: 3, column: 10 }, + cursor: { lineNumber: 4, column: 11 }, autoCompleteSet: ['b'], }, { name: 'List of objects - external template', - cursor: { row: 0, column: 1 }, + cursor: { lineNumber: 1, column: 2 }, autoCompleteSet: [tt('a', [{}])], }, ] @@ -964,7 +951,7 @@ describe('Integration', () => { [ { name: 'Field completion as scope', - cursor: { row: 3, column: 10 }, + cursor: { lineNumber: 4, column: 11 }, autoCompleteSet: [ tt('field1.1.1', { f: 1 }, 'string'), tt('field1.1.2', { f: 1 }, 'string'), @@ -972,7 +959,7 @@ describe('Integration', () => { }, { name: 'Field completion as value', - cursor: { row: 9, column: 23 }, + cursor: { lineNumber: 10, column: 24 }, autoCompleteSet: [ { name: 'field1.1.1', meta: 'string' }, { name: 'field1.1.2', meta: 'string' }, @@ -985,7 +972,7 @@ describe('Integration', () => { contextTests('POST _search\n', MAPPING, SEARCH_KB, null, [ { name: 'initial doc start', - cursor: { row: 1, column: 0 }, + cursor: { lineNumber: 2, column: 1 }, autoCompleteSet: ['{'], prefixToAdd: '', suffixToAdd: '', @@ -1000,14 +987,14 @@ describe('Integration', () => { [ { name: 'Cursor rows after request end', - cursor: { row: 4, column: 0 }, + cursor: { lineNumber: 5, column: 1 }, autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], prefixToAdd: '', suffixToAdd: ' ', }, { name: 'Cursor just after request end', - cursor: { row: 2, column: 1 }, + cursor: { lineNumber: 3, column: 2 }, no_context: true, }, ] @@ -1041,7 +1028,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster', [ { name: 'Endpoints with slashes - no slash', - cursor: { row: 0, column: 8 }, + cursor: { lineNumber: 1, column: 9 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1057,7 +1044,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/', [ { name: 'Endpoints with slashes - before slash', - cursor: { row: 0, column: 7 }, + cursor: { lineNumber: 1, column: 8 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1070,7 +1057,7 @@ describe('Integration', () => { }, { name: 'Endpoints with slashes - on slash', - cursor: { row: 0, column: 12 }, + cursor: { lineNumber: 1, column: 13 }, autoCompleteSet: [ '_cluster/nodes/stats', '_cluster/stats', @@ -1083,7 +1070,7 @@ describe('Integration', () => { }, { name: 'Endpoints with slashes - after slash', - cursor: { row: 0, column: 13 }, + cursor: { lineNumber: 1, column: 14 }, autoCompleteSet: ['nodes/stats', 'stats'], prefixToAdd: '', suffixToAdd: '', @@ -1093,7 +1080,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/no', [ { name: 'Endpoints with slashes - after slash', - cursor: { row: 0, column: 14 }, + cursor: { lineNumber: 1, column: 15 }, autoCompleteSet: [ { name: 'nodes/stats', meta: 'endpoint' }, { name: 'stats', meta: 'endpoint' }, @@ -1107,7 +1094,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _cluster/nodes/st', [ { name: 'Endpoints with two slashes', - cursor: { row: 0, column: 20 }, + cursor: { lineNumber: 1, column: 21 }, autoCompleteSet: ['stats'], prefixToAdd: '', suffixToAdd: '', @@ -1118,7 +1105,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET ', [ { name: 'Immediately after space + method', - cursor: { row: 0, column: 4 }, + cursor: { lineNumber: 1, column: 5 }, autoCompleteSet: [ { name: '_cluster/nodes/stats', meta: 'endpoint' }, { name: '_cluster/stats', meta: 'endpoint' }, @@ -1135,7 +1122,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET cl', [ { name: 'Endpoints by subpart GET', - cursor: { row: 0, column: 6 }, + cursor: { lineNumber: 1, column: 7 }, autoCompleteSet: [ { name: '_cluster/nodes/stats', meta: 'endpoint' }, { name: '_cluster/stats', meta: 'endpoint' }, @@ -1153,7 +1140,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'POST cl', [ { name: 'Endpoints by subpart POST', - cursor: { row: 0, column: 7 }, + cursor: { lineNumber: 1, column: 8 }, no_context: true, prefixToAdd: '', suffixToAdd: '', @@ -1164,7 +1151,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?', [ { name: 'Params just after ?', - cursor: { row: 0, column: 12 }, + cursor: { lineNumber: 1, column: 13 }, autoCompleteSet: [ { name: 'filter_path', meta: 'param', insertValue: 'filter_path=' }, { name: 'format', meta: 'param', insertValue: 'format=' }, @@ -1180,7 +1167,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=', [ { name: 'Params values', - cursor: { row: 0, column: 19 }, + cursor: { lineNumber: 1, column: 20 }, autoCompleteSet: [ { name: 'json', meta: 'format' }, { name: 'yaml', meta: 'format' }, @@ -1193,7 +1180,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=yaml&', [ { name: 'Params after amp', - cursor: { row: 0, column: 24 }, + cursor: { lineNumber: 1, column: 25 }, autoCompleteSet: [ { name: 'filter_path', meta: 'param', insertValue: 'filter_path=' }, { name: 'format', meta: 'param', insertValue: 'format=' }, @@ -1209,7 +1196,7 @@ describe('Integration', () => { contextTests(null, MAPPING, CLUSTER_KB, 'GET _search?format=yaml&search', [ { name: 'Params on existing param', - cursor: { row: 0, column: 26 }, + cursor: { lineNumber: 1, column: 27 }, rangeToReplace: { start: { lineNumber: 1, column: 25 }, end: { lineNumber: 1, column: 31 }, @@ -1234,7 +1221,7 @@ describe('Integration', () => { [ { name: 'Params on existing value', - cursor: { row: 0, column: 37 }, + cursor: { lineNumber: 1, column: 38 }, rangeToReplace: { start: { lineNumber: 1, column: 37 }, end: { lineNumber: 1, column: 40 }, @@ -1257,7 +1244,7 @@ describe('Integration', () => { [ { name: 'Params on just after = with existing value', - cursor: { row: 0, column: 36 }, + cursor: { lineNumber: 1, column: 37 }, rangeToReplace: { start: { lineNumber: 1, column: 37 }, end: { lineNumber: 1, column: 37 }, @@ -1286,7 +1273,7 @@ describe('Integration', () => { [ { name: 'fullurl - existing dictionary key, no template', - cursor: { row: 1, column: 6 }, + cursor: { lineNumber: 2, column: 7 }, initialValue: 'query', addTemplate: false, prefixToAdd: '', @@ -1299,7 +1286,7 @@ describe('Integration', () => { }, { name: 'fullurl - existing inner dictionary key', - cursor: { row: 2, column: 7 }, + cursor: { lineNumber: 3, column: 8 }, initialValue: 'field', addTemplate: false, prefixToAdd: '', @@ -1312,7 +1299,7 @@ describe('Integration', () => { }, { name: 'fullurl - existing dictionary key, yes template', - cursor: { row: 4, column: 7 }, + cursor: { lineNumber: 5, column: 8 }, initialValue: 'facets', addTemplate: true, prefixToAdd: '', diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js similarity index 77% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js rename to src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js index f828106e2bd41..ae66b1aa8e755 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/editor.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/__tests__/sense_editor.test.js @@ -16,17 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; +import '../sense_editor.test.mocks'; + import $ from 'jquery'; import _ from 'lodash'; -import ace from 'brace'; -import 'brace/mode/json'; -import { initializeEditor } from '../../src/input'; +import { create } from '../create'; const editorInput1 = require('./editor_input1.txt'); -const utils = require('../../src/utils'); - -const aceRange = ace.acequire('ace/range'); +const utils = require('../../../../lib/utils/utils'); describe('Editor', () => { let input; @@ -40,26 +37,22 @@ describe('Editor', () => {
    `; - input = initializeEditor( - $('#ConAppEditor'), - $('#ConAppEditorActions'), + input = create( + document.querySelector('#ConAppEditor') ); - input.$el.show(); + $(input.getCoreEditor().getContainer()).show(); input.autocomplete._test.removeChangeListener(); }); afterEach(function () { - input.$el.hide(); + $(input.getCoreEditor().getContainer()).hide(); input.autocomplete._test.addChangeListener(); }); let testCount = 0; - const callWithEditorMethod = (editorMethod, fn) => (done) => { - try { - input[editorMethod](results => fn(results, done)); - } catch { - done(); - } + const callWithEditorMethod = (editorMethod, fn) => async (done) => { + const results = await input[editorMethod](); + fn(results, done); }; function utilsTest(name, prefix, data, testToRun) { @@ -79,10 +72,9 @@ describe('Editor', () => { data = prefix; } - test('Utils test ' + id + ' : ' + name, function (done) { - input.update(data, function () { - testToRun(done); - }); + test('Utils test ' + id + ' : ' + name, async function (done) { + await input.update(data, true); + testToRun(done); }); } @@ -126,8 +118,7 @@ describe('Editor', () => { simpleRequest.prefix, simpleRequest.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - compareRequest(range, expected); + compareRequest(range, { start: { lineNumber: 1, column: 1 }, end: { lineNumber: 4, column: 2 } }); done(); }) ); @@ -152,8 +143,10 @@ describe('Editor', () => { ' ' + simpleRequest.prefix, simpleRequest.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - expect(range).toEqual(expected); + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 4, column: 2 } + }); done(); }) ); @@ -180,8 +173,10 @@ describe('Editor', () => { simpleRequest.prefix + ' ', simpleRequest.data + ' ', callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 3, 1); - compareRequest(range, expected); + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 4, column: 2 } + }); done(); }) ); @@ -208,8 +203,10 @@ describe('Editor', () => { singleLineRequest.prefix, singleLineRequest.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 1, 32); - compareRequest(range, expected); + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 33 } + }); done(); }) ); @@ -234,8 +231,10 @@ describe('Editor', () => { getRequestNoData.prefix, '\n', callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 0, 10); - compareRequest(range, expected); + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 1, column: 11 }, + }); done(); }) ); @@ -260,8 +259,10 @@ describe('Editor', () => { getRequestNoData.prefix, getRequestNoData.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 0, 10); - expect(range).toEqual(expected); + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 1, column: 11 }, + }); done(); }) ); @@ -286,8 +287,10 @@ describe('Editor', () => { multiDocRequest.prefix, multiDocRequest.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 2, 14); - expect(range).toEqual(expected); + expect(range).toEqual({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 3, column: 15 }, + }); done(); }) ); @@ -323,8 +326,10 @@ describe('Editor', () => { scriptRequest.prefix, scriptRequest.data, callWithEditorMethod('getRequestRange', (range, done) => { - const expected = new aceRange.Range(0, 0, 5, 1); - compareRequest(range, expected); + compareRequest(range, { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 6, column: 2 }, + }); done(); }) ); @@ -347,24 +352,23 @@ describe('Editor', () => { ); function multiReqTest(name, editorInput, range, expected) { - utilsTest('multi request select - ' + name, editorInput, function (done) { - input.getRequestsInRange(range, function (requests) { - // convert to format returned by request. - _.each(expected, function (req) { - req.data = - req.data == null ? [] : [JSON.stringify(req.data, null, 2)]; - }); - - compareRequest(requests, expected); - done(); + utilsTest('multi request select - ' + name, editorInput, async function (done) { + const requests = await input.getRequestsInRange(range, false); + // convert to format returned by request. + _.each(expected, function (req) { + req.data = + req.data == null ? [] : [JSON.stringify(req.data, null, 2)]; }); + + compareRequest(requests, expected); + done(); }); } multiReqTest( 'mid body to mid body', editorInput1, - { start: { row: 12 }, end: { row: 17 } }, + { start: { lineNumber: 13 }, end: { lineNumber: 18 } }, [ { method: 'PUT', @@ -386,7 +390,7 @@ describe('Editor', () => { multiReqTest( 'single request start to end', editorInput1, - { start: { row: 10 }, end: { row: 13 } }, + { start: { lineNumber: 11 }, end: { lineNumber: 14 } }, [ { method: 'PUT', @@ -401,7 +405,7 @@ describe('Editor', () => { multiReqTest( 'start to end, with comment', editorInput1, - { start: { row: 6 }, end: { row: 13 } }, + { start: { lineNumber: 7 }, end: { lineNumber: 14 } }, [ { method: 'GET', @@ -421,7 +425,7 @@ describe('Editor', () => { multiReqTest( 'before start to after end, with comments', editorInput1, - { start: { row: 4 }, end: { row: 14 } }, + { start: { lineNumber: 5 }, end: { lineNumber: 15 } }, [ { method: 'GET', @@ -441,37 +445,36 @@ describe('Editor', () => { multiReqTest( 'between requests', editorInput1, - { start: { row: 21 }, end: { row: 22 } }, + { start: { lineNumber: 22 }, end: { lineNumber: 23 } }, [] ); multiReqTest( 'between requests - with comment', editorInput1, - { start: { row: 20 }, end: { row: 22 } }, + { start: { lineNumber: 21 }, end: { lineNumber: 23 } }, [] ); multiReqTest( 'between requests - before comment', editorInput1, - { start: { row: 19 }, end: { row: 22 } }, + { start: { lineNumber: 20 }, end: { lineNumber: 23 } }, [] ); function multiReqCopyAsCurlTest(name, editorInput, range, expected) { - utilsTest('multi request copy as curl - ' + name, editorInput, function (done) { - input.getRequestsAsCURL(range, function (curl) { - expect(curl).toEqual(expected); - done(); - }); + utilsTest('multi request copy as curl - ' + name, editorInput, async function (done) { + const curl = await input.getRequestsAsCURL('http://localhost:9200', range); + expect(curl).toEqual(expected); + done(); }); } multiReqCopyAsCurlTest( 'start to end, with comment', editorInput1, - { start: { row: 6 }, end: { row: 13 } }, + { start: { lineNumber: 7 }, end: { lineNumber: 14 } }, ` curl -XGET "http://localhost:9200/_stats?level=shards" diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts new file mode 100644 index 0000000000000..33508a2f63299 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/create.ts @@ -0,0 +1,32 @@ +/* + * 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 { SenseEditor } from './sense_editor'; +import * as core from '../legacy_core_editor'; + +export function create(element: HTMLElement) { + const coreEditor = core.create(element); + const senseEditor = new SenseEditor(coreEditor); + + /** + * Init the editor + */ + senseEditor.highlightCurrentRequestsAndUpdateActionBar(); + return senseEditor; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts new file mode 100644 index 0000000000000..d56a9cca0ef32 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/curl.ts @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +function detectCURLinLine(line: string) { + // returns true if text matches a curl request + return line.match(/^\s*?curl\s+(-X[A-Z]+)?\s*['"]?.*?['"]?(\s*$|\s+?-d\s*?['"])/); +} + +export function detectCURL(text: string) { + // returns true if text matches a curl request + if (!text) return false; + for (const line of text.split('\n')) { + if (detectCURLinLine(line)) { + return true; + } + } + return false; +} + +export function parseCURL(text: string) { + let state = 'NONE'; + const out = []; + let body: any[] = []; + let line = ''; + const lines = text.trim().split('\n'); + let matches; + + const EmptyLine = /^\s*$/; + const Comment = /^\s*(?:#|\/{2,})(.*)\n?$/; + const ExecutionComment = /^\s*#!/; + const ClosingSingleQuote = /^([^']*)'/; + const ClosingDoubleQuote = /^((?:[^\\"]|\\.)*)"/; + const EscapedQuotes = /^((?:[^\\"']|\\.)+)/; + + const LooksLikeCurl = /^\s*curl\s+/; + const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/; + + const HasProtocol = /[\s"']https?:\/\//; + const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/; + const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/; + const CurlData = /^.+\s(--data|-d)\s*/; + const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/; + + if (lines.length > 0 && ExecutionComment.test(lines[0])) { + lines.shift(); + } + + function nextLine() { + if (line.length > 0) { + return true; + } + if (lines.length === 0) { + return false; + } + line = lines.shift()!.replace(/[\r\n]+/g, '\n') + '\n'; + return true; + } + + function unescapeLastBodyEl() { + const str = body.pop().replace(/\\([\\"'])/g, '$1'); + body.push(str); + } + + // Is the next char a single or double quote? + // If so remove it + function detectQuote() { + if (line.substr(0, 1) === "'") { + line = line.substr(1); + state = 'SINGLE_QUOTE'; + } else if (line.substr(0, 1) === '"') { + line = line.substr(1); + state = 'DOUBLE_QUOTE'; + } else { + state = 'UNQUOTED'; + } + } + + // Body is finished - append to output with final LF + function addBodyToOut() { + if (body.length > 0) { + out.push(body.join('')); + body = []; + } + state = 'LF'; + out.push('\n'); + } + + // If the pattern matches, then the state is about to change, + // so add the capture to the body and detect the next state + // Otherwise add the whole line + function consumeMatching(pattern: string | RegExp) { + const result = line.match(pattern); + if (result) { + body.push(result[1]); + line = line.substr(result[0].length); + detectQuote(); + } else { + body.push(line); + line = ''; + } + } + + function parseCurlLine() { + let verb = 'GET'; + let request = ''; + let result; + if ((result = line.match(CurlVerb))) { + verb = result[1]; + } + + // JS regexen don't support possessive quantifiers, so + // we need two distinct patterns + const pattern = HasProtocol.test(line) ? CurlRequestWithProto : CurlRequestWithoutProto; + + if ((result = line.match(pattern))) { + request = result[1]; + } + + out.push(verb + ' /' + request + '\n'); + + if ((result = line.match(CurlData))) { + line = line.substr(result[0].length); + detectQuote(); + if (EmptyLine.test(line)) { + line = ''; + } + } else { + state = 'NONE'; + line = ''; + out.push(''); + } + } + + while (nextLine()) { + if (state === 'SINGLE_QUOTE') { + consumeMatching(ClosingSingleQuote); + } else if (state === 'DOUBLE_QUOTE') { + consumeMatching(ClosingDoubleQuote); + unescapeLastBodyEl(); + } else if (state === 'UNQUOTED') { + consumeMatching(EscapedQuotes); + if (body.length) { + unescapeLastBodyEl(); + } + if (state === 'UNQUOTED') { + addBodyToOut(); + line = ''; + } + } + + // the BODY state (used to match the body of a Sense request) + // can be terminated early if it encounters + // a comment or an empty line + else if (state === 'BODY') { + if (Comment.test(line) || EmptyLine.test(line)) { + addBodyToOut(); + } else { + body.push(line); + line = ''; + } + } else if (EmptyLine.test(line)) { + if (state !== 'LF') { + out.push('\n'); + state = 'LF'; + } + line = ''; + } else if ((matches = line.match(Comment))) { + out.push('#' + matches[1] + '\n'); + state = 'NONE'; + line = ''; + } else if (LooksLikeCurl.test(line)) { + parseCurlLine(); + } else if ((matches = line.match(SenseLine))) { + out.push(matches[1] + ' /' + matches[2] + '\n'); + line = ''; + state = 'BODY'; + } + + // Nothing else matches, so output with a prefix of ### for debugging purposes + else { + out.push('### ' + line); + line = ''; + } + } + + addBodyToOut(); + return out.join('').trim(); +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts new file mode 100644 index 0000000000000..9310de2724fbe --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './create'; +export * from '../legacy_core_editor/create_readonly'; +export { MODE } from '../../../lib/row_parser'; +export { SenseEditor } from './sense_editor'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts new file mode 100644 index 0000000000000..8df9bb8ef9a0b --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.test.mocks.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint no-undef: 0 */ + +import '../legacy_core_editor/legacy_core_editor.test.mocks'; + +// TODO: Remove this mock +jest.mock('../../../application', () => ({ legacyBackDoorToSettings: () => {} })); + +import jQuery from 'jquery'; +jest.spyOn(jQuery, 'ajax').mockImplementation( + () => + new Promise(() => { + // never resolve + }) as any +); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts new file mode 100644 index 0000000000000..9679eaa2884ce --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/sense_editor/sense_editor.ts @@ -0,0 +1,502 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import RowParser from '../../../lib/row_parser'; +import * as utils from '../../../lib/utils/utils'; +// @ts-ignore +import * as es from '../../../lib/es/es'; + +import { CoreEditor, Position, Range } from '../../../types'; +import { createTokenIterator } from '../../factories'; + +import Autocomplete from '../../../lib/autocomplete/autocomplete'; + +export class SenseEditor { + currentReqRange: (Range & { markerRef: any }) | null; + parser: any; + + // @ts-ignore + private readonly autocomplete: any; + + constructor(private readonly coreEditor: CoreEditor) { + this.currentReqRange = null; + this.parser = new RowParser(this.coreEditor); + this.autocomplete = new (Autocomplete as any)({ + coreEditor, + parser: this.parser, + }); + this.coreEditor.on( + 'tokenizerUpdate', + this.highlightCurrentRequestsAndUpdateActionBar.bind(this) + ); + this.coreEditor.on('changeCursor', this.highlightCurrentRequestsAndUpdateActionBar.bind(this)); + this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this)); + } + + prevRequestStart = (rowOrPos?: number | Position): Position => { + let curRow: number; + + if (rowOrPos == null) { + curRow = this.coreEditor.getCurrentPosition().lineNumber; + } else if (_.isObject(rowOrPos)) { + curRow = (rowOrPos as Position).lineNumber; + } else { + curRow = rowOrPos as number; + } + + while (curRow > 0 && !this.parser.isStartRequestRow(curRow, this.coreEditor)) curRow--; + + return { + lineNumber: curRow, + column: 1, + }; + }; + + nextRequestStart = (rowOrPos?: number | Position) => { + let curRow: number; + if (rowOrPos == null) { + curRow = this.coreEditor.getCurrentPosition().lineNumber; + } else if (_.isObject(rowOrPos)) { + curRow = (rowOrPos as Position).lineNumber; + } else { + curRow = rowOrPos as number; + } + const maxLines = this.coreEditor.getValue().split('\n').length; + for (; curRow < maxLines - 1; curRow++) { + if (this.parser.isStartRequestRow(curRow, this.coreEditor)) { + break; + } + } + return { + row: curRow, + column: 0, + }; + }; + + autoIndent = _.debounce(async () => { + await this.coreEditor.waitForLatestTokens(); + const reqRange = await this.getRequestRange(); + if (!reqRange) { + return; + } + const parsedReq = await this.getRequest(); + + if (!parsedReq) { + return; + } + + if (parsedReq.data && parsedReq.data.length > 0) { + let indent = parsedReq.data.length === 1; // unindent multi docs by default + let formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent); + if (!formattedData.changed) { + // toggle. + indent = !indent; + formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent); + } + parsedReq.data = formattedData.data; + + this.replaceRequestRange(parsedReq, reqRange); + } + }, 25); + + update = async (data: string, reTokenizeAll = false) => { + return this.coreEditor.setValue(data, reTokenizeAll); + }; + + replaceRequestRange = (newRequest: any, requestRange: Range) => { + const text = utils.textFromRequest(newRequest); + if (requestRange) { + this.coreEditor.replaceRange(requestRange, text); + } else { + // just insert where we are + this.coreEditor.insert(this.coreEditor.getCurrentPosition(), text); + } + }; + + getRequestRange = async (lineNumber?: number): Promise => { + await this.coreEditor.waitForLatestTokens(); + + if (this.parser.isInBetweenRequestsRow(lineNumber)) { + return null; + } + + const reqStart = this.prevRequestStart(lineNumber); + const reqEnd = this.nextRequestEnd(reqStart); + + return { + start: { + ...reqStart, + }, + end: { + ...reqEnd, + }, + }; + }; + + expandRangeToRequestEdges = async ( + range = this.coreEditor.getSelectionRange() + ): Promise => { + await this.coreEditor.waitForLatestTokens(); + + let startLineNumber = range.start.lineNumber; + let endLineNumber = range.end.lineNumber; + const maxLine = Math.max(1, this.coreEditor.getLineCount()); + + if (this.parser.isInBetweenRequestsRow(startLineNumber)) { + /* Do nothing... */ + } else { + for (; startLineNumber >= 1; startLineNumber--) { + if (this.parser.isStartRequestRow(startLineNumber)) { + break; + } + } + } + + if (startLineNumber < 1 || startLineNumber > endLineNumber) { + return null; + } + // move end row to the previous request end if between requests, otherwise walk forward + if (this.parser.isInBetweenRequestsRow(endLineNumber)) { + for (; endLineNumber >= startLineNumber; endLineNumber--) { + if (this.parser.isEndRequestRow(endLineNumber)) { + break; + } + } + } else { + for (; endLineNumber <= maxLine; endLineNumber++) { + if (this.parser.isEndRequestRow(endLineNumber)) { + break; + } + } + } + + if (endLineNumber < startLineNumber || endLineNumber > maxLine) { + return null; + } + + const endColumn = + (this.coreEditor.getLineValue(endLineNumber) || '').replace(/\s+$/, '').length + 1; + return { + start: { + lineNumber: startLineNumber, + column: 1, + }, + end: { + lineNumber: endLineNumber, + column: endColumn, + }, + }; + }; + + getRequestInRange = async (range?: Range) => { + await this.coreEditor.waitForLatestTokens(); + if (!range) { + return null; + } + const request: { + method: string; + data: string[]; + url: string | null; + range: Range; + } = { + method: '', + data: [], + url: null, + range, + }; + + const pos = range.start; + const tokenIter = createTokenIterator({ editor: this.coreEditor, position: pos }); + let t = tokenIter.getCurrentToken(); + if (this.parser.isEmptyToken(t)) { + // if the row starts with some spaces, skip them. + t = this.parser.nextNonEmptyToken(tokenIter); + } + if (t == null) { + return null; + } + + request.method = t.value; + t = this.parser.nextNonEmptyToken(tokenIter); + + if (!t || t.type === 'method') { + return null; + } + + request.url = ''; + + while (t && t.type && t.type.indexOf('url') === 0) { + request.url += t.value; + t = tokenIter.stepForward(); + } + if (this.parser.isEmptyToken(t)) { + // if the url row ends with some spaces, skip them. + t = this.parser.nextNonEmptyToken(tokenIter); + } + let bodyStartLineNumber = (t ? 0 : 1) + tokenIter.getCurrentPosition().lineNumber; // artificially increase end of docs. + let dataEndPos: Position; + while ( + bodyStartLineNumber < range.end.lineNumber || + (bodyStartLineNumber === range.end.lineNumber && 1 < range.end.column) + ) { + dataEndPos = this.nextDataDocEnd({ + lineNumber: bodyStartLineNumber, + column: 1, + }); + const bodyRange: Range = { + start: { + lineNumber: bodyStartLineNumber, + column: 1, + }, + end: dataEndPos, + }; + const data = this.coreEditor.getValueInRange(bodyRange)!; + request.data.push(data.trim()); + bodyStartLineNumber = dataEndPos.lineNumber + 1; + } + + return request; + }; + + getRequestsInRange = async ( + range = this.coreEditor.getSelectionRange(), + includeNonRequestBlocks = false + ): Promise => { + await this.coreEditor.waitForLatestTokens(); + if (!range) { + return []; + } + + const expandedRange = await this.expandRangeToRequestEdges(range); + + if (!expandedRange) { + return []; + } + + const requests: any = []; + + let rangeStartCursor = expandedRange.start.lineNumber; + const endLineNumber = expandedRange.end.lineNumber; + + // move to the next request start (during the second iterations this may not be exactly on a request + let currentLineNumber = expandedRange.start.lineNumber; + + const flushNonRequestBlock = () => { + if (includeNonRequestBlocks) { + const nonRequestPrefixBlock = this.coreEditor + .getLines(rangeStartCursor, currentLineNumber - 1) + .join('\n'); + if (nonRequestPrefixBlock) { + requests.push(nonRequestPrefixBlock); + } + } + }; + + while (currentLineNumber <= endLineNumber) { + if (this.parser.isStartRequestRow(currentLineNumber)) { + flushNonRequestBlock(); + const request = await this.getRequest(currentLineNumber); + if (!request) { + // Something has probably gone wrong. + return requests; + } else { + requests.push(request); + rangeStartCursor = currentLineNumber = request.range.end.lineNumber + 1; + } + } else { + ++currentLineNumber; + } + } + + flushNonRequestBlock(); + + return requests; + }; + + getRequest = async (row?: number) => { + await this.coreEditor.waitForLatestTokens(); + if (this.parser.isInBetweenRequestsRow(row)) { + return null; + } + + const range = await this.getRequestRange(row); + return this.getRequestInRange(range!); + }; + + moveToPreviousRequestEdge = async () => { + await this.coreEditor.waitForLatestTokens(); + const pos = this.coreEditor.getCurrentPosition(); + for ( + pos.lineNumber--; + pos.lineNumber > 1 && !this.parser.isRequestEdge(pos.lineNumber); + pos.lineNumber-- + ) { + // loop for side effects + } + this.coreEditor.moveCursorToPosition({ + lineNumber: pos.lineNumber, + column: 1, + }); + }; + + moveToNextRequestEdge = async (moveOnlyIfNotOnEdge: boolean) => { + await this.coreEditor.waitForLatestTokens(); + const pos = this.coreEditor.getCurrentPosition(); + const maxRow = this.coreEditor.getLineCount(); + if (!moveOnlyIfNotOnEdge) { + pos.lineNumber++; + } + for ( + ; + pos.lineNumber < maxRow && !this.parser.isRequestEdge(pos.lineNumber); + pos.lineNumber++ + ) { + // loop for side effects + } + this.coreEditor.moveCursorToPosition({ + lineNumber: pos.lineNumber, + column: 1, + }); + }; + + nextRequestEnd = (pos: Position): Position => { + pos = pos || this.coreEditor.getCurrentPosition(); + const maxLines = this.coreEditor.getLineCount(); + let curLineNumber = pos.lineNumber; + for (; curLineNumber <= maxLines; ++curLineNumber) { + const curRowMode = this.parser.getRowParseMode(curLineNumber); + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) { + break; + } + } + + const column = + (this.coreEditor.getLineValue(curLineNumber) || '').replace(/\s+$/, '').length + 1; + + return { + lineNumber: curLineNumber, + column, + }; + }; + + nextDataDocEnd = (pos: Position): Position => { + pos = pos || this.coreEditor.getCurrentPosition(); + let curLineNumber = pos.lineNumber; + const maxLines = this.coreEditor.getLineCount(); + for (; curLineNumber < maxLines; curLineNumber++) { + const curRowMode = this.parser.getRowParseMode(curLineNumber); + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if ((curRowMode & this.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) { + break; + } + // eslint-disable-next-line no-bitwise + if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) { + break; + } + } + + const column = + (this.coreEditor.getLineValue(curLineNumber) || '').length + + 1 /* Range goes to 1 after last char */; + + return { + lineNumber: curLineNumber, + column, + }; + }; + + highlightCurrentRequestsAndUpdateActionBar = _.debounce(async () => { + await this.coreEditor.waitForLatestTokens(); + const expandedRange = await this.expandRangeToRequestEdges(); + if (expandedRange === null && this.currentReqRange === null) { + return; + } + if ( + expandedRange !== null && + this.currentReqRange !== null && + expandedRange.start.lineNumber === this.currentReqRange.start.lineNumber && + expandedRange.end.lineNumber === this.currentReqRange.end.lineNumber + ) { + // same request, now see if we are on the first line and update the action bar + const cursorLineNumber = this.coreEditor.getCurrentPosition().lineNumber; + if (cursorLineNumber === this.currentReqRange.start.lineNumber) { + this.updateActionsBar(); + } + return; // nothing to do.. + } + + if (this.currentReqRange) { + this.coreEditor.removeMarker(this.currentReqRange.markerRef); + } + + this.currentReqRange = expandedRange as any; + if (this.currentReqRange) { + this.currentReqRange.markerRef = this.coreEditor.addMarker(this.currentReqRange); + } + this.updateActionsBar(); + }, 25); + + getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise => { + const requests = await this.getRequestsInRange(range, true); + const result = _.map(requests, req => { + if (typeof req === 'string') { + // no request block + return req; + } + + const esPath = req.url; + const esMethod = req.method; + const esData = req.data; + + // this is the first url defined in elasticsearch.hosts + const url = es.constructESUrl(elasticsearchBaseUrl, esPath); + + let ret = 'curl -X' + esMethod + ' "' + url + '"'; + if (esData && esData.length) { + ret += " -H 'Content-Type: application/json' -d'\n"; + const dataAsString = utils.collapseLiteralStrings(esData.join('\n')); + // since Sense doesn't allow single quote json string any single qoute is within a string. + ret += dataAsString.replace(/'/g, '\\"'); + if (esData.length > 1) { + ret += '\n'; + } // end with a new line + ret += "'"; + } + return ret; + }); + + return result.join('\n'); + }; + + updateActionsBar = () => this.coreEditor.legacyUpdateUI(this.currentReqRange); + + getCoreEditor() { + return this.coreEditor; + } +} diff --git a/src/legacy/core_plugins/console/np_ready/public/application/stores/editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/stores/editor.ts rename to src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/application/stores/request.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/request.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/application/stores/request.ts rename to src/legacy/core_plugins/console/public/np_ready/application/stores/request.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/_app.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/_app.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_help.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_help.scss similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/directives/_help.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/components/_help.scss diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_history.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_history.scss similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/directives/_history.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/components/_history.scss diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss new file mode 100644 index 0000000000000..9dfef202d1254 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/components/_index.scss @@ -0,0 +1,2 @@ +@import 'help'; +@import 'history'; diff --git a/src/legacy/core_plugins/console/public/quarantined/index.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss similarity index 81% rename from src/legacy/core_plugins/console/public/quarantined/index.scss rename to src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss index ce40b2c6f1102..dc45f6cfdacf5 100644 --- a/src/legacy/core_plugins/console/public/quarantined/index.scss +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/index.scss @@ -7,5 +7,5 @@ // conChart__legend--small // conChart__legend-isLoading -@import './app'; -@import './src/directives/index'; +@import 'app'; +@import 'components/index'; diff --git a/src/legacy/core_plugins/console/np_ready/public/index.ts b/src/legacy/core_plugins/console/public/np_ready/index.ts similarity index 93% rename from src/legacy/core_plugins/console/np_ready/public/index.ts rename to src/legacy/core_plugins/console/public/np_ready/index.ts index 3f8d162f62d44..045420f401e3b 100644 --- a/src/legacy/core_plugins/console/np_ready/public/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../../core/public'; +import { PluginInitializerContext } from 'kibana/public'; import { ConsoleUIPlugin } from './plugin'; diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/index.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts similarity index 84% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts index aecb6cb37ee81..00bfe32c85906 100644 --- a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.test.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.test.ts @@ -17,15 +17,17 @@ * under the License. */ -// @ts-ignore -import '../../../../public/quarantined/tests/src/setup_mocks'; +import '../../application/models/sense_editor/sense_editor.test.mocks'; -import { Editor } from 'brace'; import $ from 'jquery'; -// @ts-ignore -import { initializeEditor } from '../../../../public/quarantined/src/input.ts'; -import { AceTokensProvider } from '.'; +// TODO: +// We import from application models as a convenient way to bootstrap loading up of an editor using +// this lib. We also need to import application specific mocks which is not ideal. +// In this situation, the token provider lib knows about app models in tests, which it really shouldn't. Should create +// a better sandbox in future. +import { create, SenseEditor } from '../../application/models/sense_editor'; + import { Position, Token, TokensProvider } from '../../types'; interface RunTestArgs { @@ -34,7 +36,7 @@ interface RunTestArgs { } describe('Ace (legacy) token provider', () => { - let aceEditor: Editor & { $el: any; autocomplete: any; update: any }; + let senseEditor: SenseEditor; let tokenProvider: TokensProvider; beforeEach(() => { // Set up our document body @@ -44,16 +46,18 @@ describe('Ace (legacy) token provider', () => {
    `; - aceEditor = initializeEditor($('#ConAppEditor'), $('#ConAppEditorActions')); + senseEditor = create(document.querySelector('#ConAppEditor')!); + + $(senseEditor.getCoreEditor().getContainer())!.show(); - aceEditor.$el.show(); - aceEditor.autocomplete._test.removeChangeListener(); - tokenProvider = new AceTokensProvider(aceEditor.session); + (senseEditor as any).autocomplete._test.removeChangeListener(); + tokenProvider = senseEditor.getCoreEditor().getTokenProvider(); }); - afterEach(done => { - aceEditor.$el.hide(); - aceEditor.autocomplete._test.addChangeListener(); - aceEditor.update('', done); + + afterEach(async () => { + $(senseEditor.getCoreEditor().getContainer())!.hide(); + (senseEditor as any).autocomplete._test.addChangeListener(); + await senseEditor.update('', true); }); describe('#getTokens', () => { @@ -63,7 +67,7 @@ describe('Ace (legacy) token provider', () => { done, lineNumber = 1, }: RunTestArgs & { expectedTokens: Token[] | null; lineNumber?: number }) => { - aceEditor.update(input, function() { + senseEditor.update(input, true).then(() => { const tokens = tokenProvider.getTokens(lineNumber); expect(tokens).toEqual(expectedTokens); if (done) done(); @@ -160,7 +164,7 @@ describe('Ace (legacy) token provider', () => { test('case 2 - empty lines', done => { runTest({ input: `GET http://test:user@somehost/ - + @@ -182,7 +186,7 @@ describe('Ace (legacy) token provider', () => { done, position, }: RunTestArgs & { expectedToken: Token | null; position: Position }) => { - aceEditor.update(input, function() { + senseEditor.update(input, true).then(() => { const tokens = tokenProvider.getTokenAt(position); expect(tokens).toEqual(expectedToken); if (done) done(); diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.ts b/src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/ace_token_provider/token_provider.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/ace_token_provider/token_provider.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js index 154a7e9ba2b4b..77c211a71d986 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_autocomplete.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_autocomplete.test.js @@ -16,18 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; +import '../../../application/models/sense_editor/sense_editor.test.mocks'; + const _ = require('lodash'); import { URL_PATH_END_MARKER, UrlPatternMatcher, ListComponent -} from '../../src/autocomplete/components'; +} from '../../autocomplete/components'; -import { populateContext } from '../../src/autocomplete/engine'; +import { populateContext } from '../../autocomplete/engine'; describe('Url autocomplete', () => { function patternsTest( diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js similarity index 94% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js index 7b44d91fa503a..b91c463bb14ff 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/url_params.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/__tests__/url_params.test.js @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; +import '../../../application/models/sense_editor/sense_editor.test.mocks'; import 'brace'; import 'brace/mode/javascript'; import 'brace/mode/json'; const _ = require('lodash'); -import { UrlParams } from '../../src/autocomplete/url_params'; -import { populateContext } from '../../src/autocomplete/engine'; +import { UrlParams } from '../../autocomplete/url_params'; +import { populateContext } from '../../autocomplete/engine'; describe('Url params', () => { function paramTest( diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts similarity index 94% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts index 47edf42f0eec5..8edb26f7817e4 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/autocomplete.ts @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import ace, { Editor as AceEditor, IEditSession, Position as AcePosition } from 'brace'; +import ace, { Editor as AceEditor, IEditSession } from 'brace'; import { i18n } from '@kbn/i18n'; import { @@ -27,17 +27,18 @@ import { getGlobalAutocompleteComponents, getUnmatchedEndpointComponents, // @ts-ignore -} from './kb'; -// @ts-ignore -import utils from './utils'; +} from '../kb/kb'; + +import * as utils from '../utils/utils'; + // @ts-ignore -import { populateContext } from './autocomplete/engine'; +import { populateContext } from './engine'; // @ts-ignore -import { URL_PATH_END_MARKER } from './autocomplete/components'; -import { createTokenIterator } from '../../../np_ready/public/application/factories'; +import { URL_PATH_END_MARKER } from './components/index'; +import { createTokenIterator } from '../../application/factories'; -import { Position, Token, Range } from '../../../np_ready/public/types'; -import { LegacyEditor } from '../../../np_ready/public/application/models'; +import { Position, Token, Range, CoreEditor } from '../../types'; +import { SenseEditor } from '../../application/models/sense_editor'; let LAST_EVALUATED_TOKEN: any = null; @@ -54,7 +55,7 @@ function isUrlParamsToken(token: any) { } } function getCurrentMethodAndTokenPaths( - editor: LegacyEditor, + editor: CoreEditor, pos: Position, parser: any, forceEndOfUrl?: boolean @@ -297,12 +298,12 @@ function getCurrentMethodAndTokenPaths( } return ret; } -export function getEndpointFromPosition(aceEditor: AceEditor, pos: AcePosition, parser: any) { - const editor = new LegacyEditor(aceEditor); +export function getEndpointFromPosition(senseEditor: SenseEditor, pos: Position, parser: any) { + const editor = senseEditor.getCoreEditor(); const context = { ...getCurrentMethodAndTokenPaths( editor, - { column: pos.column + 1, lineNumber: pos.row + 1 }, + { column: pos.column, lineNumber: pos.lineNumber }, parser, true ), @@ -313,23 +314,7 @@ export function getEndpointFromPosition(aceEditor: AceEditor, pos: AcePosition, } // eslint-disable-next-line -export default function({ - coreEditor: editor, - parser, - execCommand, - getCursorPosition, - isCompleterActive, - addChangeListener, - removeChangeListener, -}: { - coreEditor: LegacyEditor; - parser: any; - execCommand: (cmd: string) => void; - getCursorPosition: () => Position | null; - isCompleterActive: () => boolean; - addChangeListener: (fn: any) => void; - removeChangeListener: (fn: any) => void; -}) { +export default function({ coreEditor: editor, parser }: { coreEditor: CoreEditor; parser: any }) { function isUrlPathToken(token: Token | null) { switch ((token || ({} as any)).type) { case 'url.slash': @@ -404,7 +389,7 @@ export default function({ valueToInsert = context.prefixToAdd + valueToInsert + context.suffixToAdd; // disable listening to the changes we are making. - removeChangeListener(editorChangeListener); + editor.off('changeSelection', editorChangeListener); if (context.rangeToReplace.start.column !== context.rangeToReplace.end.column) { editor.replace(context.rangeToReplace, valueToInsert); @@ -456,10 +441,10 @@ export default function({ } // re-enable listening to typing - addChangeListener(editorChangeListener); + editor.on('changeSelection', editorChangeListener); } - function getAutoCompleteContext(ctxEditor: LegacyEditor, pos: Position) { + function getAutoCompleteContext(ctxEditor: CoreEditor, pos: Position) { // deduces all the parameters need to position and insert the auto complete const context: any = { autoCompleteSet: null, // instructions for what can be here @@ -567,10 +552,8 @@ export default function({ // in between request on an empty if (editor.getLineValue(pos.lineNumber).trim() === '') { - // check if the previous line is a single line begging of a new request - rowMode = parser.getRowParseMode( - pos.lineNumber - 1 - 1 /* see RowParser for why the added -1, for now */ - ); + // check if the previous line is a single line beginning of a new request + rowMode = parser.getRowParseMode(pos.lineNumber - 1); // eslint-disable-next-line no-bitwise if ( // eslint-disable-next-line no-bitwise @@ -964,23 +947,23 @@ export default function({ } LAST_EVALUATED_TOKEN = currentToken; - execCommand('startAutocomplete'); + editor.execCommand('startAutocomplete'); }, 100); function editorChangeListener() { - const position = getCursorPosition(); - if (position && !isCompleterActive()) { + const position = editor.getCurrentPosition(); + if (position && !editor.isCompleterActive()) { evaluateCurrentTokenAfterAChange(position); } } function getCompletions( DO_NOT_USE: AceEditor, - session: IEditSession, - pos: any, - prefix: any, - callback: any + DO_NOT_USE_SESSION: IEditSession, + pos: { row: number; column: number }, + prefix: string, + callback: (...args: any[]) => void ) { const position: Position = { lineNumber: pos.row + 1, @@ -1054,7 +1037,7 @@ export default function({ } } - addChangeListener(editorChangeListener); + editor.on('changeSelection', editorChangeListener); // Hook into Ace @@ -1090,8 +1073,8 @@ export default function({ _test: { getCompletions, addReplacementInfoToContext, - addChangeListener: () => addChangeListener(editorChangeListener), - removeChangeListener: () => removeChangeListener(editorChangeListener), + addChangeListener: () => editor.on('changeSelection', editorChangeListener), + removeChangeListener: () => editor.off('changeSelection', editorChangeListener), }, }; } diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/body_completer.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/body_completer.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/accept_endpoint_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/accept_endpoint_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/accept_endpoint_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/accept_endpoint_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/autocomplete_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/autocomplete_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/conditional_proxy.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/conditional_proxy.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/conditional_proxy.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/conditional_proxy.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/constant_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/constant_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/constant_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/constant_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js index b2424bebf1b9d..e07db78c4cca9 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/field_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/field_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function FieldGenerator(context) { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/global_only_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/global_only_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/global_only_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/global_only_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/id_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/id_autocomplete_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/id_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/id_autocomplete_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js index 33e27852caff2..03d67c9e27ee8 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/index_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/index_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function nonValidIndexType(token) { return !(token === '_all' || token[0] !== '_'); diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/list_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/list_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/list_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/list_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js index 4db392e60ff83..5cff4a15647ce 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/object_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/object_component.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { SharedComponent } from '.'; +import { SharedComponent } from './index'; /** * @param constants list of components that represent constant keys * @param patternsAndWildCards list of components that represent patterns and should be matched only if diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/shared_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/shared_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/shared_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/shared_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/simple_param_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/simple_param_component.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/simple_param_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/simple_param_component.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js similarity index 95% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js index 0c00b2f93ee6f..cc62a2f9eeea6 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/template_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/template_autocomplete_component.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; export class TemplateAutocompleteComponent extends ListComponent { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js index 4f1c85213b689..ca317fec9e27f 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/type_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/type_autocomplete_component.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; import { ListComponent } from './list_component'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; function TypeGenerator(context) { return mappings.getTypes(context.indices); } diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js similarity index 99% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js index 20978c2fbea60..dfae1382bed9b 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/url_pattern_matcher.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/url_pattern_matcher.js @@ -23,7 +23,7 @@ import { AcceptEndpointComponent, ListComponent, SimpleParamComponent, -} from '.'; +} from './index'; /** * @param parametrizedComponentFactories a dict of the following structure diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js index da3c63b69c610..26b7bd5c48c99 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/components/username_autocomplete_component.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/components/username_autocomplete_component.js @@ -17,7 +17,7 @@ * under the License. */ import _ from 'lodash'; -import mappings from '../../mappings'; +import mappings from '../../mappings/mappings'; import { ListComponent } from './list_component'; function nonValidUsernameType(token) { return token[0] === '_'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/engine.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/engine.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/engine.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/engine.js diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/url_params.js b/src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/url_params.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/autocomplete/url_params.js rename to src/legacy/core_plugins/console/public/np_ready/lib/autocomplete/url_params.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js similarity index 97% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js index e1bed169bb730..bf783b3dfe8ae 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.test.js @@ -18,7 +18,7 @@ */ const _ = require('lodash'); -const curl = require('../../src/curl'); +const curl = require('../curl'); import curlTests from './curl_parsing.txt'; describe('CURL', () => { diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.txt b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/curl_parsing.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/__tests__/curl_parsing.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/src/curl.js b/src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/curl.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/curl.js rename to src/legacy/core_plugins/console/public/np_ready/lib/curl_parsing/curl.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js similarity index 96% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js index 224eab7dc925d..acc33e331c9f9 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/content_type.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/es/__tests__/content_type.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { getContentType } from '../../src/es'; +import { getContentType } from '../es'; const APPLICATION_JSON = 'application/json'; describe('Content type', () => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/es.js b/src/legacy/core_plugins/console/public/np_ready/lib/es/es.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/es.js rename to src/legacy/core_plugins/console/public/np_ready/lib/es/es.js diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js similarity index 95% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js index 1618573f911ed..ad29f9808a6c2 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/kb.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/__tests__/kb.test.js @@ -18,14 +18,11 @@ */ import _ from 'lodash'; -import { populateContext } from '../../src/autocomplete/engine'; - -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; -const kb = require('../../src/kb'); -const mappings = require('../../src/mappings'); +import { populateContext } from '../../autocomplete/engine'; + +import '../../../application/models/sense_editor/sense_editor.test.mocks'; +const kb = require('../../kb'); +const mappings = require('../../mappings/mappings'); describe('Knowledge base', () => { beforeEach(() => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/kb/api.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/api.js similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/src/kb/api.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/api.js diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/kb/index.js similarity index 96% rename from src/legacy/core_plugins/console/np_ready/public/application/models/index.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/index.js index c4ac6f1454b7f..383ebef57da92 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/index.js @@ -17,4 +17,4 @@ * under the License. */ -export * from './legacy_editor'; +export * from './kb'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/kb.js b/src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/kb.js rename to src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js index 6a7edf5bc337f..ffba14fad3f37 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/kb.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/kb/kb.js @@ -24,12 +24,12 @@ import { ListComponent, TemplateAutocompleteComponent, UsernameAutocompleteComponent, -} from './autocomplete/components'; +} from '../autocomplete/components'; import $ from 'jquery'; import _ from 'lodash'; -import Api from './kb/api'; +import Api from './api'; let ACTIVE_API = new Api(); const isNotAnIndexName = name => name[0] === '_' && name !== '_all'; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js similarity index 97% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js index d79f3c50b8373..875a16402e394 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/mapping.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/__tests__/mapping.test.js @@ -16,11 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import './setup_mocks'; -import 'brace'; -import 'brace/mode/javascript'; -import 'brace/mode/json'; -const mappings = require('../../src/mappings'); +import '../../../application/models/sense_editor/sense_editor.test.mocks'; +const mappings = require('../mappings'); describe('Mappings', () => { beforeEach(() => { diff --git a/src/legacy/core_plugins/console/public/quarantined/src/mappings.js b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js similarity index 98% rename from src/legacy/core_plugins/console/public/quarantined/src/mappings.js rename to src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js index 69f122a607836..b0acf369260e9 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/mappings.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/mappings/mappings.js @@ -17,11 +17,11 @@ * under the License. */ -import { legacyBackDoorToSettings } from '../../../np_ready/public/application'; +import { legacyBackDoorToSettings } from '../../application'; const $ = require('jquery'); const _ = require('lodash'); -const es = require('./es'); +const es = require('../es/es'); // NOTE: If this value ever changes to be a few seconds or less, it might introduce flakiness // due to timing issues in our app.js tests. diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts b/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts new file mode 100644 index 0000000000000..b56d15e178810 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/row_parser.ts @@ -0,0 +1,151 @@ +/* + * 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 { CoreEditor, Token } from '../types'; +import { TokenIterator } from './token_iterator'; + +export const MODE = { + REQUEST_START: 2, + IN_REQUEST: 4, + MULTI_DOC_CUR_DOC_END: 8, + REQUEST_END: 16, + BETWEEN_REQUESTS: 32, +}; + +// eslint-disable-next-line import/no-default-export +export default class RowParser { + constructor(private readonly editor: CoreEditor) {} + + MODE = MODE; + + getRowParseMode(lineNumber = this.editor.getCurrentPosition().lineNumber) { + const linesCount = this.editor.getLineCount(); + if (lineNumber > linesCount || lineNumber < 1) { + return MODE.BETWEEN_REQUESTS; + } + const mode = this.editor.getLineState(lineNumber); + if (!mode) { + return MODE.BETWEEN_REQUESTS; + } // shouldn't really happen + + if (mode !== 'start') { + return MODE.IN_REQUEST; + } + let line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (!line || line[0] === '#') { + return MODE.BETWEEN_REQUESTS; + } // empty line or a comment waiting for a new req to start + + if (line.indexOf('}', line.length - 1) >= 0) { + // check for a multi doc request (must start a new json doc immediately after this one end. + lineNumber++; + if (lineNumber < linesCount + 1) { + line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (line.indexOf('{') === 0) { + // next line is another doc in a multi doc + // eslint-disable-next-line no-bitwise + return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST; + } + } + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request + } + + // check for single line requests + lineNumber++; + if (lineNumber >= linesCount + 1) { + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_START | MODE.REQUEST_END; + } + line = (this.editor.getLineValue(lineNumber) || '').trim(); + if (line.indexOf('{') !== 0) { + // next line is another request + // eslint-disable-next-line no-bitwise + return MODE.REQUEST_START | MODE.REQUEST_END; + } + + return MODE.REQUEST_START; + } + + rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: any) { + const mode = this.getRowParseMode(lineNumber); + // eslint-disable-next-line no-bitwise + return (mode & value) > 0; + } + + isEndRequestRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.REQUEST_END); + } + + isRequestEdge(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + // eslint-disable-next-line no-bitwise + return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START); + } + + isStartRequestRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.REQUEST_START); + } + + isInBetweenRequestsRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS); + } + + isInRequestsRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.IN_REQUEST); + } + + isMultiDocDocEndRow(row?: number, _e?: CoreEditor) { + const editor = _e || this.editor; + return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END); + } + + isEmptyToken(tokenOrTokenIter: TokenIterator | Token | null) { + const token = + tokenOrTokenIter && (tokenOrTokenIter as TokenIterator).getCurrentToken + ? (tokenOrTokenIter as TokenIterator).getCurrentToken() + : tokenOrTokenIter; + return !token || (token as Token).type === 'whitespace'; + } + + isUrlOrMethodToken(tokenOrTokenIter: TokenIterator | Token) { + const t = (tokenOrTokenIter as TokenIterator)?.getCurrentToken() ?? (tokenOrTokenIter as Token); + return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0); + } + + nextNonEmptyToken(tokenIter: TokenIterator) { + let t = tokenIter.stepForward(); + while (t && this.isEmptyToken(t)) { + t = tokenIter.stepForward(); + } + return t; + } + + prevNonEmptyToken(tokenIter: TokenIterator) { + let t = tokenIter.stepBackward(); + // empty rows return null token. + while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t)) + t = tokenIter.stepBackward(); + return t; + } +} diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/index.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.test.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.test.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.test.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.test.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.ts b/src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/lib/token_iterator/token_iterator.ts rename to src/legacy/core_plugins/console/public/np_ready/lib/token_iterator/token_iterator.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js similarity index 91% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js index a139aa47b911f..827beadee0f0b 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils.test.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils.test.js @@ -18,12 +18,25 @@ */ const _ = require('lodash'); -const utils = require('../../src/utils'); +const utils = require('../utils'); const collapsingTests = require('./utils_string_collapsing.txt'); const expandingTests = require('./utils_string_expanding.txt'); describe('Utils class', () => { + describe('collapseLiteralStrings', () => { + it('will collapse multiline strings', () => { + const multiline = '{ "foo": """bar\nbaz""" }'; + expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\nbaz" }'); + }); + + it('will collapse multiline strings with CRLF endings', () => { + const multiline = '{ "foo": """bar\r\nbaz""" }'; + expect(utils.collapseLiteralStrings(multiline)).toEqual('{ "foo": "bar\\r\\nbaz" }'); + }); + }); + + _.each(collapsingTests.split(/^=+$/m), function (fixture) { if (fixture.trim() === '') { return; diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_collapsing.txt similarity index 100% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_collapsing.txt diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt similarity index 79% rename from src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt index 34bf0f3bc20e7..88467ab3672cd 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/__tests__/utils_string_expanding.txt @@ -40,3 +40,15 @@ Correctly parse with JSON embedded inside values "content\\\\": """ { "json": "inside\\" ok! 1 " } """ } +========== +Correctly handle new lines in triple quotes +------------------------------------- +{ + "query": "\n SELECT * FROM \"TABLE\"\n " +} +------------------------------------- +{ + "query": """ + SELECT * FROM "TABLE" + """ +} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/utils.js b/src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts similarity index 77% rename from src/legacy/core_plugins/console/public/quarantined/src/utils.js rename to src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts index 5b6bd1646c300..a7f59acf1d77b 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/utils.js +++ b/src/legacy/core_plugins/console/public/np_ready/lib/utils/utils.ts @@ -19,52 +19,50 @@ import _ from 'lodash'; -const utils = {}; - -utils.textFromRequest = function (request) { +export function textFromRequest(request: any) { let data = request.data; if (typeof data !== 'string') { data = data.join('\n'); } return request.method + ' ' + request.url + '\n' + data; -}; +} -utils.jsonToString = function (data, indent) { +export function jsonToString(data: any, indent: boolean) { return JSON.stringify(data, null, indent ? 2 : 0); -}; +} -utils.reformatData = function (data, indent) { +export function formatRequestBodyDoc(data: string[], indent: boolean) { let changed = false; const formattedData = []; for (let i = 0; i < data.length; i++) { const curDoc = data[i]; try { - let newDoc = utils.jsonToString(JSON.parse(utils.collapseLiteralStrings(curDoc)), indent ? 2 : 0); + let newDoc = jsonToString(JSON.parse(collapseLiteralStrings(curDoc)), indent); if (indent) { - newDoc = utils.expandLiteralStrings(newDoc); + newDoc = expandLiteralStrings(newDoc); } changed = changed || newDoc !== curDoc; formattedData.push(newDoc); - } - catch (e) { + } catch (e) { + // eslint-disable-next-line no-console console.log(e); formattedData.push(curDoc); } } return { - changed: changed, - data: formattedData + changed, + data: formattedData, }; -}; +} -utils.collapseLiteralStrings = function (data) { +export function collapseLiteralStrings(data: any) { const splitData = data.split(`"""`); for (let idx = 1; idx < splitData.length - 1; idx += 2) { splitData[idx] = JSON.stringify(splitData[idx]); } return splitData.join(''); -}; +} /* The following regex describes global match on: @@ -81,39 +79,38 @@ utils.collapseLiteralStrings = function (data) { const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g; -utils.expandLiteralStrings = function (data) { - return data.replace(LITERAL_STRING_CANDIDATES, function (match, string) { +export function expandLiteralStrings(data: string) { + return data.replace(LITERAL_STRING_CANDIDATES, (match, string) => { // Expand to triple quotes if there are _any_ slashes if (string.match(/\\./)) { const firstDoubleQuoteIdx = string.indexOf('"'); const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx); const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length); - const jsonValue = JSON.parse(rawStringifiedValue) - .replace('^\s*\n', '') - .replace('\n\s*^', ''); + // Remove one level of JSON stringification + const jsonValue = JSON.parse(rawStringifiedValue); return `${colonAndAnySpacing}"""${jsonValue}"""`; } else { return string; } }); -}; +} -utils.extractDeprecationMessages = function (warnings) { +export function extractDeprecationMessages(warnings: string) { // pattern for valid warning header const re = /\d{3} [0-9a-zA-Z!#$%&'*+-.^_`|~]+ \"((?:\t| |!|[\x23-\x5b]|[\x5d-\x7e]|[\x80-\xff]|\\\\|\\")*)\"(?: \"[^"]*\")?/; // split on any comma that is followed by an even number of quotes - return _.map(utils.splitOnUnquotedCommaSpace(warnings), function (warning) { + return _.map(splitOnUnquotedCommaSpace(warnings), warning => { const match = re.exec(warning); // extract the actual warning if there was a match - return '#! Deprecation: ' + (match !== null ? utils.unescape(match[1]) : warning); + return '#! Deprecation: ' + (match !== null ? unescape(match[1]) : warning); }); -}; +} -utils.unescape = function (s) { +export function unescape(s: string) { return s.replace(/\\\\/g, '\\').replace(/\\"/g, '"'); -}; +} -utils.splitOnUnquotedCommaSpace = function (s) { +export function splitOnUnquotedCommaSpace(s: string) { let quoted = false; const arr = []; let buffer = ''; @@ -136,6 +133,4 @@ utils.splitOnUnquotedCommaSpace = function (s) { } arr.push(buffer); return arr; -}; - -export default utils; +} diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/public/np_ready/plugin.ts similarity index 90% rename from src/legacy/core_plugins/console/np_ready/public/plugin.ts rename to src/legacy/core_plugins/console/public/np_ready/plugin.ts index 37758adc98d11..cbe262b124677 100644 --- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/console/public/np_ready/plugin.ts @@ -20,9 +20,8 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from '../../../../../core/public'; -import { XPluginSet } from './legacy'; +import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from 'src/core/public'; +import { XPluginSet } from '../legacy'; export class ConsoleUIPlugin implements Plugin { // @ts-ignore @@ -30,7 +29,7 @@ export class ConsoleUIPlugin implements Plugin { async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) { const { - __LEGACY: { I18nContext }, + __LEGACY: { I18nContext, elasticsearchUrl, category }, dev_tools, home, } = pluginSet; @@ -46,7 +45,7 @@ export class ConsoleUIPlugin implements Plugin { icon: 'consoleApp', path: '/app/kibana#/dev_tools/console', showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, + category, }); dev_tools.register({ @@ -63,6 +62,7 @@ export class ConsoleUIPlugin implements Plugin { docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, I18nContext, notifications, + elasticsearchUrl, }), element ); diff --git a/src/legacy/core_plugins/console/np_ready/public/services/history.ts b/src/legacy/core_plugins/console/public/np_ready/services/history.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/history.ts rename to src/legacy/core_plugins/console/public/np_ready/services/history.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/index.ts b/src/legacy/core_plugins/console/public/np_ready/services/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/index.ts rename to src/legacy/core_plugins/console/public/np_ready/services/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/settings.ts b/src/legacy/core_plugins/console/public/np_ready/services/settings.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/settings.ts rename to src/legacy/core_plugins/console/public/np_ready/services/settings.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/services/storage.ts b/src/legacy/core_plugins/console/public/np_ready/services/storage.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/services/storage.ts rename to src/legacy/core_plugins/console/public/np_ready/services/storage.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/common.ts b/src/legacy/core_plugins/console/public/np_ready/types/common.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/common.ts rename to src/legacy/core_plugins/console/public/np_ready/types/common.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts similarity index 62% rename from src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts rename to src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts index fdd2e2639e554..8de4c78333fee 100644 --- a/src/legacy/core_plugins/console/np_ready/public/types/core_editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/types/core_editor.ts @@ -20,6 +20,15 @@ import { TokensProvider } from './tokens_provider'; import { Token } from './token'; +type MarkerRef = any; + +export type EditorEvent = + | 'tokenizerUpdate' + | 'changeCursor' + | 'changeScrollTop' + | 'change' + | 'changeSelection'; + export interface Position { /** * The line number, not zero-indexed. @@ -95,6 +104,13 @@ export interface CoreEditor { */ getValue(): string; + /** + * Sets the contents of the editor. + * + * Returns a promise so that callers can wait for re-tokenizing to complete. + */ + setValue(value: string, forceRetokenize: boolean): Promise; + /** * Get the contents of the editor at a specific line. */ @@ -120,6 +136,11 @@ export interface CoreEditor { */ clearSelection(): void; + /** + * Returns the {@link Range} for currently selected text + */ + getSelectionRange(): Range; + /** * Move the cursor to the indicated position. */ @@ -152,4 +173,83 @@ export interface CoreEditor { * Get line content between and including the start and end lines provided. */ getLines(startLine: number, endLine: number): string[]; + + /** + * Replace a range in the current buffer with the provided value. + */ + replaceRange(range: Range, value: string): void; + + /** + * Return the current line count in the buffer. + */ + getLineCount(): number; + + /** + * A legacy mechanism which gives consumers of this interface a chance to wait for + * latest tokenization to complete. + */ + waitForLatestTokens(): Promise; + + /** + * Mark a range in the current buffer + */ + addMarker(range: Range): MarkerRef; + + /** + * Mark a range in the current buffer + */ + removeMarker(ref: MarkerRef): void; + + /** + * Get a number that represents the current wrap limit on a line + */ + getWrapLimit(): number; + + /** + * Register a listener for predefined editor events + */ + on(event: EditorEvent, listener: () => void): void; + + /** + * Unregister a listener for predefined editor events + */ + off(event: EditorEvent, listener: () => void): void; + + /** + * Execute a predefined editor command. + */ + execCommand(cmd: string): void; + + /** + * Returns a boolean value indicating whether or not the completer UI is currently showing in + * the editor + */ + isCompleterActive(): boolean; + + /** + * Get the HTML container element for this editor instance + */ + getContainer(): HTMLDivElement; + + /** + * Because the core editor should not know about requests, but can know about ranges we still + * have this backdoor to update UI in response to request range changes, for example, as the user + * moves the cursor around + */ + legacyUpdateUI(opts: any): void; + + /** + * A method to for the editor to resize, useful when, for instance, window size changes. + */ + resize(): void; + + /** + * Expose a way to set styles on the editor + */ + setStyles(styles: { wrapLines: boolean; fontSize: string }): void; + + /** + * Register a keyboard shortcut and provide a function to be called. + */ + registerKeyboardShortcut(opts: { keys: any; fn: () => void; name: string }): void; } diff --git a/src/legacy/core_plugins/console/np_ready/public/types/index.ts b/src/legacy/core_plugins/console/public/np_ready/types/index.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/index.ts rename to src/legacy/core_plugins/console/public/np_ready/types/index.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/token.ts b/src/legacy/core_plugins/console/public/np_ready/types/token.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/token.ts rename to src/legacy/core_plugins/console/public/np_ready/types/token.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/types/tokens_provider.ts b/src/legacy/core_plugins/console/public/np_ready/types/tokens_provider.ts similarity index 100% rename from src/legacy/core_plugins/console/np_ready/public/types/tokens_provider.ts rename to src/legacy/core_plugins/console/public/np_ready/types/tokens_provider.ts diff --git a/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss b/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss deleted file mode 100644 index 56bd7ed20bda4..0000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/directives/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './help'; -@import './history'; diff --git a/src/legacy/core_plugins/console/public/quarantined/src/input.ts b/src/legacy/core_plugins/console/public/quarantined/src/input.ts deleted file mode 100644 index eb93f8e165cb5..0000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/input.ts +++ /dev/null @@ -1,74 +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. - */ - -// @ts-ignore -import Autocomplete from './autocomplete'; - -import { LegacyEditor } from '../../../np_ready/public/application/models'; - -// @ts-ignore -import SenseEditor from './sense_editor/editor'; -import { Position } from '../../../np_ready/public/types'; - -let input: any; -export function initializeEditor($el: JQuery, $actionsEl: JQuery) { - input = new SenseEditor($el); - - // Autocomplete should not use any Ace functionality directly - // so we build a shim here. - const editorShim = { - coreEditor: new LegacyEditor(input), - parser: input.parser, - execCommand: (cmd: string) => input.execCommand(cmd), - getCursorPosition: (): Position | null => { - if (input.selection && input.selection.lead) { - return { - lineNumber: input.selection.lead.row + 1, - column: input.selection.lead.column + 1, - }; - } - return null; - }, - isCompleterActive: () => { - return Boolean(input.__ace.completer && input.__ace.completer.activated); - }, - addChangeListener: (fn: any) => input.on('changeSelection', fn), - removeChangeListener: (fn: any) => input.off('changeSelection', fn), - }; - - input.autocomplete = new (Autocomplete as any)(editorShim); - input.setOptions({ - enableBasicAutocompletion: true, - }); - input.$blockScrolling = Infinity; - input.$actions = $actionsEl; - - /** - * Init the editor - */ - input.focus(); - input.highlightCurrentRequestsAndUpdateActionBar(); - - return input; -} - -// eslint-disable-next-line -export default function getInput() { - return input; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js b/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js deleted file mode 100644 index 9c34860df35ff..0000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/editor.js +++ /dev/null @@ -1,676 +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. - */ - -const _ = require('lodash'); -const ace = require('brace'); -const $ = require('jquery'); -const curl = require('../curl'); -const RowParser = require('./row_parser'); -const InputMode = require('./mode/input'); -const utils = require('../utils'); -const es = require('../es'); -const chrome = require('ui/chrome'); - -const smartResize = require('../smart_resize'); - -function createInstance($el) { - const aceEditor = ace.edit($el[0]); - - // we must create a custom class for each instance, so that the prototype - // can be the unique aceEditor it extends - const CustomSenseEditor = function () { - }; - CustomSenseEditor.prototype = {}; - - function bindProp(key) { - Object.defineProperty(CustomSenseEditor.prototype, key, { - get: function () { - return aceEditor[key]; - }, - set: function (val) { - aceEditor[key] = val; - } - }); - } - - // iterate all of the accessible properties/method, on the prototype and beyond - // eslint-disable-next-line guard-for-in - for (const key in aceEditor) { - switch (typeof aceEditor[key]) { - case 'function': - CustomSenseEditor.prototype[key] = _.bindKey(aceEditor, key); - break; - default: - bindProp(key); - break; - } - } - - const editor = new CustomSenseEditor(); - editor.__ace = aceEditor; - return editor; -} - -export default function SenseEditor($el) { - const editor = createInstance($el); - let CURRENT_REQ_RANGE = null; - - editor.$el = $el; - // place holder for an action bar, needs to be set externally. - editor.$actions = null; - - // mixin the RowParser - editor.parser = new RowParser(editor); - editor.resize = smartResize(editor); - - // dirty check for tokenizer state, uses a lot less cycles - // than listening for tokenizerUpdate - const onceDoneTokenizing = function (callback, cancelAlreadyScheduledCalls) { - const session = editor.getSession(); - let timer = false; - const checkInterval = 25; - - return function () { - const self = this; - const args = [].slice.call(arguments, 0); - - if (cancelAlreadyScheduledCalls) { - timer = clearTimeout(timer); - } - - setTimeout(function check() { - // If the bgTokenizer doesn't exist, we can assume that the underlying editor has been - // torn down, e.g. by closing the History tab, and we don't need to do anything further. - if (session.bgTokenizer) { - // Wait until the bgTokenizer is done running before executing the callback. - if (session.bgTokenizer.running) { - timer = setTimeout(check, checkInterval); - } else { - callback.apply(self, args); - } - } - }); - }; - }; - - editor.setShowPrintMargin(false); - (function (session) { - session.setMode(new InputMode.Mode()); - session.setFoldStyle('markbeginend'); - session.setTabSize(2); - session.setUseWrapMode(true); - }(editor.getSession())); - - editor.prevRequestStart = function (rowOrPos) { - rowOrPos = _.isUndefined(rowOrPos) || rowOrPos === null ? editor.getCursorPosition() : rowOrPos; - - let curRow = _.isObject(rowOrPos) ? rowOrPos.row : rowOrPos; - while (curRow > 0 && !editor.parser.isStartRequestRow(curRow, editor)) curRow--; - - return { - row: curRow, - column: 0 - }; - }; - - editor.nextRequestStart = function (rowOrPos) { - rowOrPos = _.isUndefined(rowOrPos) || rowOrPos === null ? editor.getCursorPosition() : rowOrPos; - const session = editor.getSession(); - let curRow = _.isObject(rowOrPos) ? rowOrPos.row : rowOrPos; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - if (editor.parser.isStartRequestRow(curRow, editor)) { - break; - } - } - return { - row: curRow, - column: 0 - }; - }; - - editor.autoIndent = onceDoneTokenizing(function () { - editor.getRequestRange(function (reqRange) { - if (!reqRange) { - return; - } - editor.getRequest(function (parsedReq) { - if (parsedReq.data && parsedReq.data.length > 0) { - let indent = parsedReq.data.length === 1; // unindent multi docs by default - let formattedData = utils.reformatData(parsedReq.data, indent); - if (!formattedData.changed) { - // toggle. - indent = !indent; - formattedData = utils.reformatData(parsedReq.data, indent); - } - parsedReq.data = formattedData.data; - - editor.replaceRequestRange(parsedReq, reqRange); - } - }); - }); - }, true); - - editor.update = function (data, callback) { - callback = typeof callback === 'function' ? callback : null; - const session = editor.getSession(); - - session.setValue(data); - if (callback) { - // force update of tokens, but not on this thread to allow for ace rendering. - setTimeout(function () { - let i; - for (i = 0; i < session.getLength(); i++) { - session.getTokens(i); - } - callback(); - }); - } - - }; - - editor.replaceRequestRange = function (newRequest, requestRange) { - const text = utils.textFromRequest(newRequest); - if (requestRange) { - const pos = editor.getCursorPosition(); - editor.getSession().replace(requestRange, text); - const maxRow = Math.max(requestRange.start.row + text.split('\n').length - 1, 0); - pos.row = Math.min(pos.row, maxRow); - editor.moveCursorToPosition(pos); - // ACE UPGRADE - check if needed - at the moment the above may trigger a selection. - editor.clearSelection(); - } - else { - // just insert where we are - editor.insert(text); - } - }; - - editor.iterForCurrentLoc = function () { - const pos = editor.getCursorPosition(); - return editor.iterForPosition(pos.row, pos.column, editor); - }; - - editor.iterForPosition = function (row, column) { - return new (ace.acequire('ace/token_iterator').TokenIterator)(editor.getSession(), row, column); - }; - - editor.getRequestRange = onceDoneTokenizing(function (row, cb) { - if (_.isUndefined(cb)) { - cb = row; - row = null; - } - if (typeof cb !== 'function') { - return; - } - - if (editor.parser.isInBetweenRequestsRow(row)) { - cb(null); - return; - } - - const reqStart = editor.prevRequestStart(row, editor); - const reqEnd = editor.nextRequestEnd(reqStart, editor); - cb(new (ace.acequire('ace/range').Range)( - reqStart.row, reqStart.column, - reqEnd.row, reqEnd.column - )); - }); - - editor.getEngulfingRequestsRange = onceDoneTokenizing(function (range, cb) { - if (_.isUndefined(cb)) { - cb = range; - range = null; - } - - range = range || editor.getSelectionRange(); - - const session = editor.getSession(); - let startRow = range.start.row; - let endRow = range.end.row; - const maxLine = Math.max(0, session.getLength() - 1); - - // move start row to the previous request start if in body, o.w. forward - if (editor.parser.isInBetweenRequestsRow(startRow)) { - //for (; startRow <= endRow; startRow++) { - // if (editor.parser.isStartRequestRow(startRow)) { - // break; - // } - //} - } - else { - for (; startRow >= 0; startRow--) { - if (editor.parser.isStartRequestRow(startRow)) { - break; - } - } - } - - if (startRow < 0 || startRow > endRow) { - cb(null); - return; - } - // move end row to the previous request end if between requests, o.w. walk forward - if (editor.parser.isInBetweenRequestsRow(endRow)) { - for (; endRow >= startRow; endRow--) { - if (editor.parser.isEndRequestRow(endRow)) { - break; - } - } - } - else { - - for (; endRow <= maxLine; endRow++) { - if (editor.parser.isEndRequestRow(endRow)) { - break; - } - } - - } - - if (endRow < startRow || endRow > maxLine) { - cb(null); - return; - } - - const endColumn = (session.getLine(endRow) || '').replace(/\s+$/, '').length; - cb(new (ace.acequire('ace/range').Range)(startRow, 0, endRow, endColumn)); - }); - - - editor.getRequestInRange = onceDoneTokenizing(function (range, cb) { - if (!range) { - return; - } - const request = { - method: '', - data: [], - url: null, - range: range - }; - - const pos = range.start; - const tokenIter = editor.iterForPosition(pos.row, pos.column, editor); - let t = tokenIter.getCurrentToken(); - if (editor.parser.isEmptyToken(t)) { - // if the row starts with some spaces, skip them. - t = editor.parser.nextNonEmptyToken(tokenIter); - } - request.method = t.value; - t = editor.parser.nextNonEmptyToken(tokenIter); - if (!t || t.type === 'method') { - return null; - } - request.url = ''; - while (t && t.type && t.type.indexOf('url') === 0) { - request.url += t.value; - t = tokenIter.stepForward(); - } - if (editor.parser.isEmptyToken(t)) { - // if the url row ends with some spaces, skip them. - t = editor.parser.nextNonEmptyToken(tokenIter); - } - - let bodyStartRow = (t ? 0 : 1) + tokenIter.getCurrentTokenRow(); // artificially increase end of docs. - let dataEndPos; - while (bodyStartRow < range.end.row || ( - bodyStartRow === range.end.row && 0 < range.end.column - )) { - dataEndPos = editor.nextDataDocEnd({ - row: bodyStartRow, - column: 0 - }); - const bodyRange = new (ace.acequire('ace/range').Range)( - bodyStartRow, 0, - dataEndPos.row, dataEndPos.column - ); - const data = editor.getSession().getTextRange(bodyRange); - request.data.push(data.trim()); - bodyStartRow = dataEndPos.row + 1; - } - - cb(request); - }); - - editor.getRequestsInRange = function (range, includeNonRequestBlocks, cb) { - if (_.isUndefined(includeNonRequestBlocks)) { - includeNonRequestBlocks = false; - cb = range; - range = null; - } else if (_.isUndefined(cb)) { - cb = includeNonRequestBlocks; - includeNonRequestBlocks = false; - } - - function explicitRangeToRequests(requestsRange, tempCb) { - if (!requestsRange) { - tempCb([]); - return; - } - - const startRow = requestsRange.start.row; - const endRow = requestsRange.end.row; - - // move to the next request start (during the second iterations this may not be exactly on a request - let currentRow = startRow; - for (; currentRow <= endRow; currentRow++) { - if (editor.parser.isStartRequestRow(currentRow)) { - break; - } - } - - let nonRequestPrefixBlock = null; - if (includeNonRequestBlocks && currentRow !== startRow) { - nonRequestPrefixBlock = editor.getSession().getLines(startRow, currentRow - 1).join('\n'); - } - - if (currentRow > endRow) { - tempCb(nonRequestPrefixBlock ? [nonRequestPrefixBlock] : []); - return; - } - - editor.getRequest(currentRow, function (request) { - if (!request) { - return; - } - explicitRangeToRequests({ - start: { - row: request.range.end.row + 1 - }, - end: { - row: requestsRange.end.row - } - }, - function (restOfRequests) { - restOfRequests.unshift(request); - if (nonRequestPrefixBlock !== null) { - restOfRequests.unshift(nonRequestPrefixBlock); - } - tempCb(restOfRequests); - } - ); - }); - } - - editor.getEngulfingRequestsRange(range, function (requestRange) { - explicitRangeToRequests(requestRange, cb); - }); - }; - - editor.getRequest = onceDoneTokenizing(function (row, cb) { - if (_.isUndefined(cb)) { - cb = row; - row = null; - } - if (typeof cb !== 'function') { - return; - } - if (editor.parser.isInBetweenRequestsRow(row)) { - cb(null); - return; - } - editor.getRequestRange(row, function (range) { - editor.getRequestInRange(range, cb); - }); - }); - - editor.moveToPreviousRequestEdge = onceDoneTokenizing(function () { - const pos = editor.getCursorPosition(); - for (pos.row--; pos.row > 0 && !editor.parser.isRequestEdge(pos.row); pos.row--) { - // loop for side effects - } - editor.moveCursorTo(pos.row, 0); - }); - - editor.moveToNextRequestEdge = onceDoneTokenizing(function (moveOnlyIfNotOnEdge) { - const pos = editor.getCursorPosition(); - const maxRow = editor.getSession().getLength(); - if (!moveOnlyIfNotOnEdge) { - pos.row++; - } - for (; pos.row < maxRow && !editor.parser.isRequestEdge(pos.row); pos.row++) { - // loop for side effects - } - editor.moveCursorTo(pos.row, 0); - }); - - editor.nextRequestEnd = function (pos) { - pos = pos || editor.getCursorPosition(); - const session = editor.getSession(); - let curRow = pos.row; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - const curRowMode = editor.parser.getRowParseMode(curRow, editor); - if ((curRowMode & editor.parser.MODE.REQUEST_END) > 0) { - break; - } - if (curRow !== pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) { - break; - } - } - - const column = (session.getLine(curRow) || '').replace(/\s+$/, '').length; - - return { - row: curRow, - column: column - }; - }; - - editor.nextDataDocEnd = function (pos) { - pos = pos || editor.getCursorPosition(); - const session = editor.getSession(); - let curRow = pos.row; - const maxLines = session.getLength(); - for (; curRow < maxLines - 1; curRow++) { - const curRowMode = editor.parser.getRowParseMode(curRow, editor); - if ((curRowMode & RowParser.REQUEST_END) > 0) { - break; - } - if ((curRowMode & editor.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) { - break; - } - if (curRow !== pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) { - break; - } - } - - const column = (session.getLine(curRow) || '').length; - - return { - row: curRow, - column: column - }; - }; - - // overwrite the actual aceEditor's onPaste method - const origOnPaste = editor.__ace.onPaste; - editor.__ace.onPaste = function (text) { - if (text && curl.detectCURL(text)) { - editor.handleCURLPaste(text); - return; - } - origOnPaste.call(this, text); - }; - - editor.handleCURLPaste = function (text) { - const curlInput = curl.parseCURL(text); - - editor.insert(curlInput); - }; - - editor.highlightCurrentRequestsAndUpdateActionBar = onceDoneTokenizing(function () { - const session = editor.getSession(); - editor.getEngulfingRequestsRange(function (newCurrentReqRange) { - if (newCurrentReqRange === null && CURRENT_REQ_RANGE === null) { - return; - } - if (newCurrentReqRange !== null && CURRENT_REQ_RANGE !== null && - newCurrentReqRange.start.row === CURRENT_REQ_RANGE.start.row && - newCurrentReqRange.end.row === CURRENT_REQ_RANGE.end.row - ) { - // same request, now see if we are on the first line and update the action bar - const cursorRow = editor.getCursorPosition().row; - if (cursorRow === CURRENT_REQ_RANGE.start.row) { - editor.updateActionsBar(); - } - return; // nothing to do.. - } - - if (CURRENT_REQ_RANGE) { - session.removeMarker(CURRENT_REQ_RANGE.marker_id); - } - - CURRENT_REQ_RANGE = newCurrentReqRange; - if (CURRENT_REQ_RANGE) { - CURRENT_REQ_RANGE.marker_id = session.addMarker(CURRENT_REQ_RANGE, 'ace_snippet-marker', 'fullLine'); - } - editor.updateActionsBar(); - }); - }, true); - - editor.getRequestsAsCURL = function (range, cb) { - if (_.isUndefined(cb)) { - cb = range; - range = null; - } - - if (_.isUndefined(cb)) { - cb = $.noop; - } - - editor.getRequestsInRange(range, true, function (requests) { - - const result = _.map(requests, function requestToCurl(req) { - - if (typeof req === 'string') { - // no request block - return req; - } - - const esPath = req.url; - const esMethod = req.method; - const esData = req.data; - - // this is the first url defined in elasticsearch.hosts - const elasticsearchBaseUrl = chrome.getInjected('elasticsearchUrl'); - const url = es.constructESUrl(elasticsearchBaseUrl, esPath); - - let ret = 'curl -X' + esMethod + ' "' + url + '"'; - if (esData && esData.length) { - ret += ' -H \'Content-Type: application/json\' -d\'\n'; - const dataAsString = utils.collapseLiteralStrings(esData.join('\n')); - // since Sense doesn't allow single quote json string any single qoute is within a string. - ret += dataAsString.replace(/'/g, '\\"'); - if (esData.length > 1) { - ret += '\n'; - } // end with a new line - ret += '\''; - } - return ret; - }); - - cb(result.join('\n')); - }); - }; - - editor.getSession().on('tokenizerUpdate', function () { - editor.highlightCurrentRequestsAndUpdateActionBar(); - }); - - editor.getSession().selection.on('changeCursor', function () { - editor.highlightCurrentRequestsAndUpdateActionBar(); - }); - - editor.updateActionsBar = (function () { - const set = function (top) { - if (top === null) { - editor.$actions.css('visibility', 'hidden'); - } - else { - editor.$actions.css({ - top: top, - visibility: 'visible' - }); - } - }; - - const hide = function () { - set(); - }; - - return function () { - if (!editor.$actions) { - return; - } - if (CURRENT_REQ_RANGE) { - // elements are positioned relative to the editor's container - // pageY is relative to page, so subtract the offset - // from pageY to get the new top value - const offsetFromPage = editor.$el.offset().top; - const startRow = CURRENT_REQ_RANGE.start.row; - const startColumn = CURRENT_REQ_RANGE.start.column; - const session = editor.session; - const firstLine = session.getLine(startRow); - const maxLineLength = session.getWrapLimit() - 5; - const isWrapping = firstLine.length > maxLineLength; - const getScreenCoords = (row) => editor.renderer.textToScreenCoordinates(row, startColumn).pageY - offsetFromPage; - const topOfReq = getScreenCoords(startRow); - - if (topOfReq >= 0) { - let offset = 0; - if (isWrapping) { - // Try get the line height of the text area in pixels. - const textArea = editor.$el.find('textArea'); - const hasRoomOnNextLine = session.getLine(startRow + 1).length < maxLineLength; - if (textArea && hasRoomOnNextLine) { - // Line height + the number of wraps we have on a line. - offset += (session.getRowLength(startRow) * textArea.height()); - } else { - if (startRow > 0) { - set(getScreenCoords(startRow - 1, startColumn)); - return; - } - set(getScreenCoords(startRow + 1, startColumn)); - return; - } - } - set(topOfReq + offset); - return; - } - - const bottomOfReq = editor.renderer.textToScreenCoordinates( - CURRENT_REQ_RANGE.end.row, - CURRENT_REQ_RANGE.end.column - ).pageY - offsetFromPage; - - if (bottomOfReq >= 0) { - set(0); - return; - } - } - - hide(); - }; - }()); - - editor.getSession().on('changeScrollTop', editor.updateActionsBar); - - return editor; -} diff --git a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js b/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js deleted file mode 100644 index c1bba107de784..0000000000000 --- a/src/legacy/core_plugins/console/public/quarantined/src/sense_editor/row_parser.js +++ /dev/null @@ -1,147 +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. - */ - -const MODE = { - REQUEST_START: 2, - IN_REQUEST: 4, - MULTI_DOC_CUR_DOC_END: 8, - REQUEST_END: 16, - BETWEEN_REQUESTS: 32 -}; - -/** - * The RowParser is still using Ace editor directly for now. - * - * This will be cleaned up when we implement the editor interface everywhere - * in the next pass. - */ -function RowParser(editor) { - const defaultEditor = editor; - - this.getRowParseMode = function (row) { - if (row === null || typeof row === 'undefined') { - row = editor.getCursorPosition().row; - } - - const session = editor.getSession(); - if (row >= session.getLength() || row < 0) { - return MODE.BETWEEN_REQUESTS; - } - const mode = session.getState(row); - if (!mode) { - return MODE.BETWEEN_REQUESTS; - } // shouldn't really happen - - if (mode !== 'start') { - return MODE.IN_REQUEST; - } - let line = (session.getLine(row) || '').trim(); - if (!line || line[0] === '#') { - return MODE.BETWEEN_REQUESTS; - } // empty line or a comment waiting for a new req to start - - if (line.indexOf('}', line.length - 1) >= 0) { - // check for a multi doc request (must start a new json doc immediately after this one end. - row++; - if (row < session.getLength()) { - line = (session.getLine(row) || '').trim(); - if (line.indexOf('{') === 0) { // next line is another doc in a multi doc - return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST; - } - - } - return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request - } - - // check for single line requests - row++; - if (row >= session.getLength()) { - return MODE.REQUEST_START | MODE.REQUEST_END; - } - line = (session.getLine(row) || '').trim(); - if (line.indexOf('{') !== 0) { // next line is another request - return MODE.REQUEST_START | MODE.REQUEST_END; - } - - return MODE.REQUEST_START; - }; - - this.rowPredicate = function (row, editor, value) { - const mode = this.getRowParseMode(row, editor); - return (mode & value) > 0; - }; - - this.isEndRequestRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_END); - }; - - this.isRequestEdge = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START); - }; - - this.isStartRequestRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.REQUEST_START); - }; - - this.isInBetweenRequestsRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS); - }; - - this.isInRequestsRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.IN_REQUEST); - }; - - this.isMultiDocDocEndRow = function (row, _e) { - const editor = _e || defaultEditor; - return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END); - }; - - this.isEmptyToken = function (tokenOrTokenIter) { - const token = tokenOrTokenIter && tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; - return !token || token.type === 'whitespace'; - }; - - this.isUrlOrMethodToken = function (tokenOrTokenIter) { - const t = tokenOrTokenIter.getCurrentToken ? tokenOrTokenIter.getCurrentToken() : tokenOrTokenIter; - return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0); - }; - - - this.nextNonEmptyToken = function (tokenIter) { - let t = tokenIter.stepForward(); - while (t && this.isEmptyToken(t)) t = tokenIter.stepForward(); - return t; - }; - - this.prevNonEmptyToken = function (tokenIter) { - let t = tokenIter.stepBackward(); - // empty rows return null token. - while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t)) t = tokenIter.stepBackward(); - return t; - }; -} - -RowParser.prototype.MODE = MODE; - -export default RowParser; From 717e40c4447977aeac1f4e316b400ee22d1a56d5 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Tue, 10 Dec 2019 15:05:05 +0000 Subject: [PATCH 33/56] Move DashboardEmptyScreen inside DashboardViewport (#51939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prototyping adding Visualization to Dashboard * i18n fixes * Remvoing dashboard empty screen directive * Updating test for empty dashboard screen * Removing unused state variable * Adding a test for DashboardViewPort * i18n & minor fixes * Fixing fullscreen mode view * Fixing failing functional test (hopefully) * Minor style fix * Fixing EUI text, rendering empty screen OR the panels * Fixing empty screen in fullscreen mode * Update snapshot * Trying to render empty screen from Angular controller * refactor: 💡 don't pass renderEmpty through inputs And make sure isEmptyState is not stale. * Fixing tests after Vadim's commit * Removing unnecessary isEmptyStateProps * Skipping failing test * Removing unnecessary en.json file * Re-adding emptyState, reintroducing functional test * Fixing ja-JP file * Undoing my thing to the functional test --- .../dashboard_empty_screen.test.tsx.snap | 364 +++++++++++++----- .../__tests__/dashboard_empty_screen.test.tsx | 10 +- .../public/dashboard/_dashboard_app.scss | 6 +- .../kibana/public/dashboard/application.ts | 1 - .../public/dashboard/dashboard_app.html | 18 - .../dashboard/dashboard_app_controller.tsx | 49 ++- .../dashboard/dashboard_empty_screen.tsx | 73 ++-- .../dashboard_empty_screen_directive.ts | 30 -- .../public/dashboard/top_nav/top_nav_ids.ts | 1 + .../public/embeddable/dashboard_container.tsx | 4 +- .../embeddable/grid/_dashboard_grid.scss | 2 +- .../viewport/dashboard_viewport.test.tsx | 51 +++ .../viewport/dashboard_viewport.tsx | 59 ++- .../public/lib/containers/container.ts | 1 + .../public/lib/embeddables/i_embeddable.ts | 2 +- .../apps/dashboard/full_screen_mode.js | 1 - .../public/does_inherit_time_range.ts | 4 + .../translations/translations/ja-JP.json | 2 +- 18 files changed, 461 insertions(+), 217 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index 07e4173d5323f..8410040a0100d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -193,78 +193,160 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` textComponent={Symbol(react.fragment)} > - - - - - -

    - This dashboard is empty. Let’s fill it up! -

    -

    - - Click the - - - - button in the menu bar above to add a visualization to the dashboard. - -

    -

    - - visit the Visualize app - , + "maxWidth": "36em", } } > - If you haven't set up any visualizations yet, - - visit the Visualize app - - to create your first visualization - -

    +
    + + +
    + + + + + + +
    + + +
    +

    + This dashboard is empty. Let’s fill it up! +

    +
    +
    + +
    + + +
    +

    + Click the + + + + button in the menu bar above to add a visualization to the dashboard. +

    +
    +
    + +
    + + +
    +

    + + visit the Visualize app + , + } + } + > + If you haven't set up any visualizations yet, + + visit the Visualize app + + to create your first visualization + +

    +
    +
    +
    + + +
    + +
    + @@ -464,51 +546,119 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] textComponent={Symbol(react.fragment)} > - - - - - -

    - This dashboard is empty. Let’s fill it up! -

    -

    - - Click the - - - - button in the menu bar above to start working on your new dashboard. - -

    + + +
    + + + + + + +
    + + +
    +

    + This dashboard is empty. Let’s fill it up! +

    +
    +
    + +
    + + +
    +

    + Click the + + + + button in the menu bar above to start working on your new dashboard. +

    +
    +
    +
    + + + + +
    + diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index a4604d17ddecd..69bdcf59bb227 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; describe('DashboardEmptyScreen', () => { const defaultProps = { @@ -26,7 +28,7 @@ describe('DashboardEmptyScreen', () => { onLinkClick: jest.fn(), }; - function mountComponent(props?: Props) { + function mountComponent(props?: DashboardEmptyScreenProps) { const compProps = props || defaultProps; const comp = mountWithIntl(); return comp; @@ -35,14 +37,14 @@ describe('DashboardEmptyScreen', () => { test('renders correctly with visualize paragraph', () => { const component = mountComponent(); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(1); }); test('renders correctly without visualize paragraph', () => { const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); expect(component).toMatchSnapshot(); - const paragraph = component.find('.linkToVisualizeParagraph'); + const paragraph = findTestSubject(component, 'linkToVisualizeParagraph'); expect(paragraph.length).toBe(0); }); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index 14c35759d70a9..d9eadf6c0e37d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,9 +6,5 @@ .dshStartScreen { text-align: center; - padding: $euiSize; - - > * { - max-width: 36em !important; - } + padding: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index b58325a77e61e..ef1bcab589c4a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -131,7 +131,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', - 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } 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 c7fd8600b73bb..3cf8932958b6d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -48,24 +48,6 @@ > -
    -
    -

    -
    - -
    - -
    - -
    - -
    -
    -

    {{screenTitle}}

    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 7eac251a532c7..3b336ebfc11fe 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 @@ -21,9 +21,10 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; +import { uniq, noop } from 'lodash'; import { Subscription } from 'rxjs'; +import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { subscribeWithScope, @@ -41,8 +42,6 @@ import { import { FilterStateManager, IndexPattern } from '../../../data/public'; import { Query, SavedQuery, IndexPatternsContract } from '../../../../../plugins/data/public'; -import './dashboard_empty_screen_directive'; - import { DashboardContainer, DASHBOARD_CONTAINER_TYPE, @@ -143,6 +142,16 @@ export class DashboardAppController { } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + $scope.getShouldShowEditHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsEditMode() && + !dashboardConfig.getHideWriteControls(); + + $scope.getShouldShowViewHelp = () => + !dashboardStateManager.getPanels().length && + dashboardStateManager.getIsViewMode() && + !dashboardConfig.getHideWriteControls(); + const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { return; @@ -171,6 +180,17 @@ export class DashboardAppController { } }; + const getEmptyScreenProps = (shouldShowEditHelp: boolean): DashboardEmptyScreenProps => { + const emptyScreenProps: DashboardEmptyScreenProps = { + onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, + showLinkToVisualize: shouldShowEditHelp, + }; + if (shouldShowEditHelp) { + emptyScreenProps.onVisualizeClick = noop; + } + return emptyScreenProps; + }; + const getDashboardInput = (): DashboardContainerInput => { const embeddablesMap: { [key: string]: DashboardPanelState; @@ -182,6 +202,8 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { expandedPanelId = dashboardContainer.getInput().expandedPanelId; } + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); return { id: dashboardStateManager.savedDashboard.id || '', filters: queryFilter.getFilters(), @@ -194,6 +216,7 @@ export class DashboardAppController { viewMode: dashboardStateManager.getViewMode(), panels: embeddablesMap, isFullScreenMode: dashboardStateManager.getFullScreenMode(), + isEmptyState: shouldShowEditHelp || shouldShowViewHelp, useMargins: dashboardStateManager.getUseMargins(), lastReloadRequestTime, title: dashboardStateManager.getTitle(), @@ -234,6 +257,15 @@ export class DashboardAppController { if (!isErrorEmbeddable(container)) { dashboardContainer = container; + dashboardContainer.renderEmpty = () => { + const shouldShowEditHelp = $scope.getShouldShowEditHelp(); + const shouldShowViewHelp = $scope.getShouldShowViewHelp(); + const isEmptyState = shouldShowEditHelp || shouldShowViewHelp; + return isEmptyState ? ( + + ) : null; + }; + updateIndexPatterns(dashboardContainer); outputSubscription = dashboardContainer.getOutput$().subscribe(() => { @@ -334,15 +366,6 @@ export class DashboardAppController { updateBreadcrumbs(); dashboardStateManager.registerChangeListener(updateBreadcrumbs); - $scope.getShouldShowEditHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsEditMode() && - !dashboardConfig.getHideWriteControls(); - $scope.getShouldShowViewHelp = () => - !dashboardStateManager.getPanels().length && - dashboardStateManager.getIsViewMode() && - !dashboardConfig.getHideWriteControls(); - const getChangesFromAppStateForContainerState = () => { const appStateDashboardInput = getDashboardInput(); if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) { @@ -729,6 +752,8 @@ export class DashboardAppController { } }; + navActions[TopNavIds.VISUALIZE] = async () => {}; + navActions[TopNavIds.OPTIONS] = anchorElement => { showOptionsPopover({ anchorElement, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx index d5a4e6e6a325d..234228ba4166a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -18,29 +18,43 @@ */ import React from 'react'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiSpacer, + EuiPageContent, + EuiPageBody, + EuiPage, + EuiText, +} from '@elastic/eui'; import * as constants from './dashboard_empty_screen_constants'; -export interface Props { +export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; + onVisualizeClick?: () => void; } -export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { +export function DashboardEmptyScreen({ + showLinkToVisualize, + onLinkClick, +}: DashboardEmptyScreenProps) { const linkToVisualizeParagraph = ( -

    - - {constants.visualizeAppLinkTest} - - ), - }} - /> -

    + +

    + + {constants.visualizeAppLinkTest} + + ), + }} + /> +

    +
    ); const paragraph = ( description1: string, @@ -50,15 +64,15 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props dataTestSubj?: string ) => { return ( -

    - + +

    {description1} {linkText} {description2} - -

    +

    + ); }; const addVisualizationParagraph = ( @@ -70,6 +84,7 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props constants.addVisualizationLinkAriaLabel, 'emptyDashboardAddPanelButton' )} + {linkToVisualizeParagraph} ); @@ -81,11 +96,19 @@ export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props ); return ( - - -

    {constants.fillDashboardTitle}

    - {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
    + + + + + + +

    {constants.fillDashboardTitle}

    +
    + + {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
    +
    +
    ); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts deleted file mode 100644 index 5ebefd817ca4a..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts +++ /dev/null @@ -1,30 +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. - */ -// @ts-ignore -import angular from 'angular'; -import { DashboardEmptyScreen } from './dashboard_empty_screen'; - -angular - .module('app/dashboard/emptyScreen', ['react']) - .directive('dashboardEmptyScreen', function(reactDirective: any) { - return reactDirective(DashboardEmptyScreen, [ - ['showLinkToVisualize', { watchDepth: 'value' }], - ['onLinkClick', { watchDepth: 'reference' }], - ]); - }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts index 9df86f2ca3cce..c67d6891c18e7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/top_nav_ids.ts @@ -26,4 +26,5 @@ export const TopNavIds = { ENTER_EDIT_MODE: 'enterEditMode', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', + VISUALIZE: 'visualize', }; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx index 684aa93779bc1..021a1a9d1e64a 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx @@ -90,6 +90,8 @@ export type DashboardReactContext = KibanaReactContext { public readonly type = DASHBOARD_CONTAINER_TYPE; + public renderEmpty?: undefined | (() => React.ReactNode); + constructor( initialInput: DashboardContainerInput, private readonly options: DashboardContainerOptions, @@ -124,7 +126,7 @@ export class DashboardContainer extends Container - + , dom diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss index 24b813ec58964..0bd356522c7fa 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/_dashboard_grid.scss @@ -34,7 +34,7 @@ .dshLayout-isMaximizedPanel { height: 100% !important; /* 1. */ width: 100%; - position: absolute; + position: absolute !important; } /** diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx index a2f7b8dc28fb0..e3d9b8552f060 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx @@ -121,6 +121,24 @@ test('renders DashboardViewport with no visualizations', () => { component.unmount(); }); +test('renders DashboardEmptyScreen', () => { + const renderEmptyScreen = jest.fn(); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true }); + const component = mount( + + + + + + ); + const dashboardEmptyScreenDiv = component.find('.dshDashboardEmptyScreen'); + expect(dashboardEmptyScreenDiv.length).toBe(1); + expect(renderEmptyScreen).toHaveBeenCalled(); + + component.unmount(); +}); + test('renders exit full screen button when in full screen mode', async () => { const { props, options } = getProps(); props.container.updateInput({ isFullScreenMode: true }); @@ -153,6 +171,39 @@ test('renders exit full screen button when in full screen mode', async () => { component.unmount(); }); +test('renders exit full screen button when in full screen mode and empty screen', async () => { + const renderEmptyScreen = jest.fn(); + renderEmptyScreen.mockReturnValue(React.createElement('div')); + const { props, options } = getProps({ renderEmpty: renderEmptyScreen }); + props.container.updateInput({ isEmptyState: true, isFullScreenMode: true }); + const component = mount( + + + + + + ); + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).toBe('ExitFullScreenButton'); + + props.container.updateInput({ isFullScreenMode: false }); + component.update(); + await nextTick(); + + expect( + (component + .find('.dshDashboardEmptyScreen') + .childAt(0) + .type() as any).name + ).not.toBe('ExitFullScreenButton'); + + component.unmount(); +}); + test('DashboardViewport unmount unsubscribes', async done => { const { props, options } = getProps(); const component = mount( diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx index 13407e5e33725..e7fd379898dd1 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.tsx @@ -26,6 +26,7 @@ import { context } from '../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; + renderEmpty?: () => React.ReactNode; } interface State { @@ -34,6 +35,7 @@ interface State { title: string; description?: string; panels: { [key: string]: PanelState }; + isEmptyState?: boolean; } export class DashboardViewport extends React.Component { @@ -44,26 +46,40 @@ export class DashboardViewport extends React.Component { - const { isFullScreenMode, useMargins, title, description } = this.props.container.getInput(); + const { + isFullScreenMode, + useMargins, + title, + description, + isEmptyState, + } = this.props.container.getInput(); if (this.mounted) { this.setState({ isFullScreenMode, description, useMargins, title, + isEmptyState, }); } }); @@ -82,19 +98,33 @@ export class DashboardViewport extends React.Component + {isFullScreenMode && ( + + )} + {renderEmpty && renderEmpty()} +
    + ); + } + + private renderContainerScreen() { const { container } = this.props; + const { isFullScreenMode, panels, title, description, useMargins } = this.state; return (
    - {this.state.isFullScreenMode && ( + {isFullScreenMode && ( @@ -103,4 +133,13 @@ export class DashboardViewport extends React.Component ); } + + public render() { + return ( + + {this.state.isEmptyState ? this.renderEmptyScreen() : null} + {this.renderContainerScreen()} + + ); + } } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index bce16747ed48e..71e7cca3552bb 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -240,6 +240,7 @@ export abstract class Container< ...this.input.panels, [panelState.explicitInput.id]: panelState, }, + isEmptyState: false, } as Partial); return await this.untilEmbeddableLoaded(panelState.explicitInput.id); diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 33cb146a056cb..0197582778940 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -28,7 +28,7 @@ export interface EmbeddableInput { id: string; lastReloadRequestTime?: number; hidePanelTitles?: boolean; - + isEmptyState?: boolean; /** * List of action IDs that this embeddable should not render. */ diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index e18fd47b39b16..bf549ec21a6d3 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -78,7 +78,6 @@ export default function ({ getService, getPageObjects }) { const logoButton = await PageObjects.dashboard.getExitFullScreenLogoButton(); await logoButton.moveMouseTo(); await PageObjects.dashboard.clickExitFullScreenTextButton(); - await retry.try(async () => { const isChromeVisible = await PageObjects.common.isChromeVisible(); expect(isChromeVisible).to.be(true); diff --git a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts index 4cfe581b7eac5..d1568a5ab96ce 100644 --- a/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts +++ b/x-pack/plugins/advanced_ui_actions/public/does_inherit_time_range.ts @@ -17,6 +17,10 @@ export function doesInheritTimeRange(embeddable: Embeddable) { // Note: this logic might not work in a container nested world... the explicit input // may be on the root... or any of the interim parents. + // if it's a dashboard emptys screen, there will be no embeddable + if (!parent.getInput().panels[embeddable.id]) { + return false; + } // If there is no explicit input defined on the parent then this embeddable inherits the // time range from whatever the time range of the parent is. return parent.getInput().panels[embeddable.id].explicitInput.timeRange === undefined; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d51332c65aa54..55147c7863d1f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12736,4 +12736,4 @@ "xpack.licensing.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", "xpack.licensing.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです" } -} +} \ No newline at end of file From 5217dfd731726aa0c3ce39436b7a5018ff2aa466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20=C3=81lvarez?= Date: Tue, 10 Dec 2019 16:11:58 +0100 Subject: [PATCH 34/56] update apm index pattern (#52629) --- .../core_plugins/kibana/server/tutorials/apm/index_pattern.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json index 69a165c09c2f9..9001613623ccb 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, From 6ea07cbb9cfa01bc428da29c2d2beedb73594f8a Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 10 Dec 2019 08:43:04 -0700 Subject: [PATCH 35/56] [SIEM][Detection Engine] Renaming and moving of folders and files (#52587) ## Summary * Creates several folders * Moves schema into smaller files * Moves `utils.ts` in smaller files * Splits apart the types to not be in one giant file but rather cascade bottom up ### 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~~ - [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) --- .../plugins/siem/server/kibana.index.ts | 16 +- .../lib/detection_engine/alerts/types.ts | 264 -- .../lib/detection_engine/alerts/utils.test.ts | 1107 --------- .../lib/detection_engine/alerts/utils.ts | 408 ---- .../routes/__mocks__/request_responses.ts | 4 +- .../{ => rules}/create_rules_route.test.ts | 6 +- .../routes/{ => rules}/create_rules_route.ts | 17 +- .../{ => rules}/delete_rules_route.test.ts | 6 +- .../routes/{ => rules}/delete_rules_route.ts | 13 +- .../{ => rules}/find_rules_route.test.ts | 6 +- .../routes/{ => rules}/find_rules_route.ts | 13 +- .../{ => rules}/read_rules_route.test.ts | 6 +- .../routes/{ => rules}/read_rules_route.ts | 13 +- .../detection_engine/routes/rules/types.ts | 11 + .../{ => rules}/update_rules_route.test.ts | 6 +- .../routes/{ => rules}/update_rules_route.ts | 13 +- .../routes/rules/utils.test.ts | 496 ++++ .../detection_engine/routes/rules/utils.ts | 76 + .../detection_engine/routes/schemas.test.ts | 2133 ----------------- .../lib/detection_engine/routes/schemas.ts | 162 -- .../schemas/create_rules_schema.test.ts | 1047 ++++++++ .../routes/schemas/create_rules_schema.ts | 67 + .../routes/schemas/find_rules_schema.test.ts | 136 ++ .../routes/schemas/find_rules_schema.ts | 24 + .../routes/schemas/query_rules_schema.test.ts | 32 + .../routes/schemas/query_rules_schema.ts | 16 + .../routes/schemas/schemas.ts | 79 + .../schemas/set_signal_status_schema.test.ts | 66 + .../schemas/set_signal_status_schema.ts | 17 + .../schemas/update_rules_schema.test.ts | 869 +++++++ .../routes/schemas/update_rules_schema.ts | 63 + .../signals/open_close_signals_route.ts | 4 +- .../lib/detection_engine/routes/utils.test.ts | 488 +--- .../lib/detection_engine/routes/utils.ts | 68 - .../{alerts => rules}/create_rules.ts | 0 .../{alerts => rules}/delete_rules.ts | 0 .../{alerts => rules}/find_rules.test.ts | 0 .../{alerts => rules}/find_rules.ts | 0 .../{alerts => rules}/read_rules.test.ts | 0 .../{alerts => rules}/read_rules.ts | 0 .../lib/detection_engine/rules/types.ts | 102 + .../{alerts => rules}/update_rules.test.ts | 0 .../{alerts => rules}/update_rules.ts | 0 .../__mocks__/es_results.ts | 19 +- .../signals/build_bulk_body.test.ts | 284 +++ .../signals/build_bulk_body.ts | 56 + .../signals/build_event_type_signal.test.ts | 47 + .../signals/build_event_type_signal.ts | 15 + .../build_events_query.test.ts | 0 .../{alerts => signals}/build_events_query.ts | 0 .../signals/build_rule.test.ts | 156 ++ .../detection_engine/signals/build_rule.ts | 59 + .../signals/build_signal.test.ts | 111 + .../detection_engine/signals/build_signal.ts | 26 + .../{alerts => signals}/get_filter.test.ts | 2 +- .../{alerts => signals}/get_filter.ts | 2 +- .../get_input_output_index.test.ts | 0 .../get_input_output_index.ts | 0 .../signals/search_after_bulk_create.test.ts | 286 +++ .../signals/search_after_bulk_create.ts | 135 ++ .../signal_rule_alert_type.ts} | 10 +- .../signals/single_bulk_create.test.ts | 230 ++ .../signals/single_bulk_create.ts | 106 + .../signals/single_search_after.test.ts | 73 + .../signals/single_search_after.ts | 52 + .../lib/detection_engine/signals/types.ts | 123 + .../lib/detection_engine/signals/utils.ts | 16 + .../siem/server/lib/detection_engine/types.ts | 67 + .../legacy/plugins/siem/server/lib/types.ts | 20 - 69 files changed, 5029 insertions(+), 4720 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/create_rules_route.test.ts (96%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/create_rules_route.ts (85%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/delete_rules_route.test.ts (96%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/delete_rules_route.ts (78%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/find_rules_route.test.ts (94%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/find_rules_route.ts (78%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/read_rules_route.test.ts (94%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/read_rules_route.ts (78%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/update_rules_route.test.ts (97%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/{ => rules}/update_rules_route.ts (84%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/create_rules.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/delete_rules.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/find_rules.test.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/find_rules.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/read_rules.test.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/read_rules.ts (100%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/update_rules.test.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => rules}/update_rules.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/__mocks__/es_results.ts (94%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/build_events_query.test.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/build_events_query.ts (100%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/get_filter.test.ts (99%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/get_filter.ts (98%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/get_input_output_index.test.ts (100%) rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts => signals}/get_input_output_index.ts (100%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts rename x-pack/legacy/plugins/siem/server/lib/detection_engine/{alerts/rules_alert_type.ts => signals/signal_rule_alert_type.ts} (96%) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index bb0958b32fa19..f56e6b3c3f550 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -6,18 +6,18 @@ import { PluginInitializerContext } from 'src/core/server'; -import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type'; -import { isAlertExecutor } from './lib/detection_engine/alerts/types'; -import { createRulesRoute } from './lib/detection_engine/routes/create_rules_route'; +import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule_alert_type'; +import { createRulesRoute } from './lib/detection_engine/routes/rules/create_rules_route'; import { createIndexRoute } from './lib/detection_engine/routes/index/create_index_route'; import { readIndexRoute } from './lib/detection_engine/routes/index/read_index_route'; -import { readRulesRoute } from './lib/detection_engine/routes/read_rules_route'; -import { findRulesRoute } from './lib/detection_engine/routes/find_rules_route'; -import { deleteRulesRoute } from './lib/detection_engine/routes/delete_rules_route'; -import { updateRulesRoute } from './lib/detection_engine/routes/update_rules_route'; +import { readRulesRoute } from './lib/detection_engine/routes/rules/read_rules_route'; +import { findRulesRoute } from './lib/detection_engine/routes/rules/find_rules_route'; +import { deleteRulesRoute } from './lib/detection_engine/routes/rules/delete_rules_route'; +import { updateRulesRoute } from './lib/detection_engine/routes/rules/update_rules_route'; import { setSignalsStatusRoute } from './lib/detection_engine/routes/signals/open_close_signals_route'; import { ServerFacade } from './types'; import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_index_route'; +import { isAlertExecutor } from './lib/detection_engine/signals/types'; const APP_ID = 'siem'; @@ -26,7 +26,7 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy const version = context.env.packageInfo.version; if (__legacy.plugins.alerting != null) { - const type = rulesAlertType({ logger, version }); + const type = signalRulesAlertType({ logger, version }); if (isAlertExecutor(type)) { __legacy.plugins.alerting.setup.registerType(type); } 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 deleted file mode 100644 index c9d265ebffacd..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ /dev/null @@ -1,264 +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 { get } from 'lodash/fp'; - -import { SIGNALS_ID } from '../../../../common/constants'; -import { - Alert, - AlertType, - State, - AlertExecutorOptions, -} from '../../../../../alerting/server/types'; -import { AlertsClient } from '../../../../../alerting/server/alerts_client'; -import { ActionsClient } from '../../../../../actions/server/actions_client'; -import { RequestFacade } from '../../../types'; -import { SearchResponse } from '../../types'; -import { esFilters } from '../../../../../../../../src/plugins/data/server'; - -export type PartialFilter = Partial; - -export interface IMitreAttack { - id: string; - name: string; - reference: string; -} -export interface ThreatParams { - framework: string; - tactic: IMitreAttack; - techniques: IMitreAttack[]; -} -export interface RuleAlertParams { - description: string; - enabled: boolean; - falsePositives: string[]; - filters: PartialFilter[] | undefined | null; - from: string; - immutable: boolean; - index: string[]; - interval: string; - ruleId: string | undefined | null; - language: string | undefined | null; - maxSignals: number; - riskScore: number; - outputIndex: string; - name: string; - query: string | undefined | null; - references: string[]; - savedId: string | undefined | null; - meta: Record | undefined | null; - severity: string; - tags: string[]; - to: string; - threats: ThreatParams[] | undefined | null; - type: 'query' | 'saved_query'; -} - -export type RuleAlertParamsRest = Omit< - RuleAlertParams, - 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' -> & { - rule_id: RuleAlertParams['ruleId']; - false_positives: RuleAlertParams['falsePositives']; - saved_id: RuleAlertParams['savedId']; - max_signals: RuleAlertParams['maxSignals']; - risk_score: RuleAlertParams['riskScore']; - output_index: RuleAlertParams['outputIndex']; -}; - -export interface SignalsParams { - signalIds: string[] | undefined | null; - query: object | undefined | null; - status: 'open' | 'closed'; -} - -export type SignalsRestParams = Omit & { - signal_ids: SignalsParams['signalIds']; -}; - -export type OutputRuleAlertRest = RuleAlertParamsRest & { - id: string; - created_by: string | undefined | null; - updated_by: string | undefined | null; -}; - -export type UpdateRuleAlertParamsRest = Partial & { - id: string | undefined; - rule_id: RuleAlertParams['ruleId'] | undefined; -}; - -export interface FindParamsRest { - per_page: number; - page: number; - sort_field: string; - sort_order: 'asc' | 'desc'; - fields: string[]; - filter: string; -} - -export interface Clients { - alertsClient: AlertsClient; - actionsClient: ActionsClient; -} - -export type RuleParams = RuleAlertParams & Clients; - -export type UpdateRuleParams = Partial & { - id: string | undefined | null; -} & Clients; - -export type DeleteRuleParams = Clients & { - id: string | undefined; - ruleId: string | undefined | null; -}; - -export interface FindRulesRequest extends Omit { - query: { - per_page: number; - page: number; - search?: string; - sort_field?: string; - filter?: string; - fields?: string[]; - sort_order?: 'asc' | 'desc'; - }; -} - -export interface FindRuleParams { - alertsClient: AlertsClient; - perPage?: number; - page?: number; - sortField?: string; - filter?: string; - fields?: string[]; - sortOrder?: 'asc' | 'desc'; -} - -export interface ReadRuleParams { - alertsClient: AlertsClient; - id?: string | undefined | null; - ruleId?: string | undefined | null; -} - -export interface ReadRuleByRuleId { - alertsClient: AlertsClient; - ruleId: string; -} - -export type RuleTypeParams = Omit; - -export type RuleAlertType = Alert & { - id: string; - params: RuleTypeParams; -}; - -export interface RulesRequest extends RequestFacade { - payload: RuleAlertParamsRest; -} - -export interface SignalsRequest extends RequestFacade { - payload: SignalsRestParams; -} - -export interface UpdateRulesRequest extends RequestFacade { - payload: UpdateRuleAlertParamsRest; -} - -export type RuleExecutorOptions = Omit & { - params: RuleAlertParams & { - scrollSize: number; - scrollLock: string; - }; -}; - -export type SearchTypes = - | string - | string[] - | number - | number[] - | boolean - | boolean[] - | object - | object[]; - -export interface SignalSource { - [key: string]: SearchTypes; - '@timestamp': string; -} - -export interface BulkResponse { - took: number; - errors: boolean; - items: [ - { - create: { - _index: string; - _type?: string; - _id: string; - _version: number; - result?: string; - _shards?: { - total: number; - successful: number; - failed: number; - }; - _seq_no?: number; - _primary_term?: number; - status: number; - error?: { - type: string; - reason: string; - index_uuid?: string; - shard: string; - index: string; - }; - }; - } - ]; -} - -export interface MGetResponse { - docs: GetResponse[]; -} -export interface GetResponse { - _index: string; - _type: string; - _id: string; - _version: number; - _seq_no: number; - _primary_term: number; - found: boolean; - _source: SearchTypes; -} - -export type SignalSearchResponse = SearchResponse; -export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; - -export type QueryRequest = Omit & { - query: { id: string | undefined; rule_id: string | undefined }; -}; - -// This returns true because by default a RuleAlertTypeDefinition is an AlertType -// since we are only increasing the strictness of params. -export const isAlertExecutor = (obj: RuleAlertTypeDefinition): obj is AlertType => { - return true; -}; - -export type RuleAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: RuleExecutorOptions) => Promise; -}; - -export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { - return obj.every(rule => isAlertType(rule)); -}; - -export const isAlertType = (obj: unknown): obj is RuleAlertType => { - return get('alertTypeId', obj) === SIGNALS_ID; -}; - -export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { - return objArray.length === 0 || isAlertType(objArray[0]); -}; 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 deleted file mode 100644 index 41052ab4bbb15..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ /dev/null @@ -1,1107 +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 uuid from 'uuid'; -import { savedObjectsClientMock } from 'src/core/server/mocks'; - -import { Logger } from '../../../../../../../../src/core/server'; -import { - buildBulkBody, - generateId, - singleBulkCreate, - singleSearchAfter, - searchAfterAndBulkCreate, - buildEventTypeSignal, - buildSignal, - buildRule, -} from './utils'; -import { - sampleDocNoSortId, - sampleRuleAlertParams, - sampleDocSearchResultsNoSortId, - sampleDocSearchResultsNoSortIdNoHits, - sampleDocSearchResultsNoSortIdNoVersion, - sampleDocSearchResultsWithSortId, - sampleEmptyDocSearchResults, - repeatedSearchResultsWithSortId, - sampleBulkCreateDuplicateResult, - sampleRuleGuid, - sampleRule, - sampleIdGuid, -} from './__mocks__/es_results'; -import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { OutputRuleAlertRest } from './types'; -import { Signal } from '../../types'; - -const mockLogger: Logger = { - log: jest.fn(), - trace: jest.fn(), - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), -}; - -const mockService = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), -}; - -describe('utils', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('buildBulkBody', () => { - test('if bulk body builds well-defined body', () => { - const sampleParams = sampleRuleAlertParams(); - const fakeSignalSourceHit = buildBulkBody({ - doc: sampleDocNoSortId(), - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - 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']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - kind: 'signal', - }, - signal: { - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - kind: 'event', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - 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']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'signal', - module: 'system', - }, - signal: { - original_event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - 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']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'signal', - module: 'system', - }, - signal: { - original_event: { - action: 'socket_opened', - dataset: 'socket', - module: 'system', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - - test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { - const sampleParams = sampleRuleAlertParams(); - const doc = sampleDocNoSortId(); - doc._source.event = { - kind: 'event', - }; - const fakeSignalSourceHit = buildBulkBody({ - doc, - ruleParams: sampleParams, - id: sampleRuleGuid, - name: 'rule-name', - createdBy: 'elastic', - 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']; - expect(fakeSignalSourceHit).toEqual({ - someKey: 'someValue', - event: { - kind: 'signal', - }, - signal: { - original_event: { - kind: 'event', - }, - parent: { - id: sampleIdGuid, - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - rule_id: 'rule-1', - false_positives: [], - max_signals: 10000, - risk_score: 50, - output_index: '.siem-signals', - description: 'Detecting root and admin users', - from: 'now-6m', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - name: 'rule-name', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - type: 'query', - to: 'now', - enabled: true, - created_by: 'elastic', - updated_by: 'elastic', - }, - }, - }); - }); - }); - describe('singleBulkCreate', () => { - describe('create signal id gereateId', () => { - test('two docs with same index, id, and version should have same id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version, ruleId); - expect(firstHash).toEqual(generatedHash); - expect(secondHash).toEqual(generatedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - }); - test('two docs with different index, id, and version should have different id', () => { - const findex = 'myfakeindex'; - const findex2 = 'mysecondfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // 'mysecondfakeindexsomefakeid1rule-1' - const secondGeneratedHash = - 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex2, fid, version, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('two docs with same index, different id, and same version should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const fid2 = 'somefakeid2'; - const version = '1'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // 'myfakeindexsomefakeid21rule-1' - const secondGeneratedHash = - '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid2, version, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('two docs with same index, same id, and different version should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const version2 = '2'; - const ruleId = 'rule-1'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // myfakeindexsomefakeid2rule-1' - const secondGeneratedHash = - 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version2, ruleId); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - test('Ensure generated id is less than 512 bytes, even for really really long strings', () => { - const longIndexName = - 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - const firstHash = generateId(longIndexName, fid, version, ruleId); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - }); - test('two docs with same index, same id, same version number, and different rule ids should have different id', () => { - const findex = 'myfakeindex'; - const fid = 'somefakeid'; - const version = '1'; - const ruleId = 'rule-1'; - const ruleId2 = 'rule-2'; - // 'myfakeindexsomefakeid1rule-1' - const firstGeneratedHash = - '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; - // myfakeindexsomefakeid1rule-2' - const secondGeneratedHash = - '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863'; - const firstHash = generateId(findex, fid, version, ruleId); - const secondHash = generateId(findex, fid, version, ruleId2); - expect(firstHash).toEqual(firstGeneratedHash); - expect(secondHash).toEqual(secondGeneratedHash); - expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field - expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); - expect(firstHash).not.toEqual(secondHash); - }); - }); - test('create successful bulk create', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create successful bulk create with docs with no versioning', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create unsuccessful bulk create due to empty search results', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleEmptyDocSearchResults; - mockService.callCluster.mockReturnValue(false); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(successfulsingleBulkCreate).toEqual(true); - }); - test('create successful bulk create when bulk create has errors', async () => { - const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(successfulsingleBulkCreate).toEqual(true); - }); - }); - describe('singleSearchAfter', () => { - test('if singleSearchAfter works without a given sort id', async () => { - let searchAfterSortId; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); - await expect( - singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }) - ).rejects.toThrow('Attempted to search after with empty sort id'); - }); - test('if singleSearchAfter works with a given sort id', async () => { - const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); - const searchAfterResult = await singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }); - expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); - }); - test('if singleSearchAfter throws error', async () => { - const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockImplementation(async () => { - throw Error('Fake Error'); - }); - await expect( - singleSearchAfter({ - searchAfterSortId, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - pageSize: 1, - filter: undefined, - }) - ).rejects.toThrow('Fake Error'); - }); - }); - describe('searchAfterAndBulkCreate', () => { - test('if successful with empty search results', async () => { - const sampleParams = sampleRuleAlertParams(); - const result = await searchAfterAndBulkCreate({ - someResult: sampleEmptyDocSearchResults, - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockService.callCluster).toHaveBeenCalledTimes(0); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs', async () => { - const sampleParams = sampleRuleAlertParams(30); - const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockService.callCluster).toHaveBeenCalledTimes(5); - expect(result).toEqual(true); - }); - test('if unsuccessful first bulk create', async () => { - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - const sampleParams = sampleRuleAlertParams(10); - mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(result).toEqual(false); - }); - test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortId(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(mockLogger.error).toHaveBeenCalled(); - expect(result).toEqual(false); - }); - test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { - const sampleParams = sampleRuleAlertParams(); - mockService.callCluster.mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }); - const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortIdNoHits(), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockReturnValueOnce(sampleEmptyDocSearchResults); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(true); - }); - test('if returns false when singleSearchAfter throws an exception', async () => { - const sampleParams = sampleRuleAlertParams(10); - const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); - mockService.callCluster - .mockReturnValueOnce({ - took: 100, - errors: false, - items: [ - { - fakeItemValue: 'fakeItemKey', - }, - ], - }) - .mockImplementation(() => { - throw Error('Fake Error'); - }); - const result = await searchAfterAndBulkCreate({ - someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), - ruleParams: sampleParams, - services: mockService, - logger: mockLogger, - id: sampleRuleGuid, - signalsIndex: DEFAULT_SIGNALS_INDEX, - name: 'rule-name', - createdBy: 'elastic', - updatedBy: 'elastic', - interval: '5m', - enabled: true, - pageSize: 1, - filter: undefined, - tags: ['some fake tag 1', 'some fake tag 2'], - }); - expect(result).toEqual(false); - }); - }); - - describe('buildEventTypeSignal', () => { - test('it returns the event appended of kind signal if it does not exist', () => { - const doc = sampleDocNoSortId(); - delete doc._source.event; - const eventType = buildEventTypeSignal(doc); - const expected: object = { kind: 'signal' }; - expect(eventType).toEqual(expected); - }); - - test('it returns the event appended of kind signal if it is an empty object', () => { - const doc = sampleDocNoSortId(); - doc._source.event = {}; - const eventType = buildEventTypeSignal(doc); - const expected: object = { kind: 'signal' }; - expect(eventType).toEqual(expected); - }); - - test('it returns the event with kind signal and other properties if they exist', () => { - const doc = sampleDocNoSortId(); - doc._source.event = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - }; - const eventType = buildEventTypeSignal(doc); - const expected: object = { - action: 'socket_opened', - module: 'system', - dataset: 'socket', - kind: 'signal', - }; - expect(eventType).toEqual(expected); - }); - }); - - describe('buildSignal', () => { - test('it builds a signal as expected without original_event if event does not exist', () => { - const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - delete doc._source.event; - const rule: Partial = sampleRule(); - const signal = buildSignal(doc, rule); - const expected: Signal = { - parent: { - id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - status: 'open', - rule: { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - }, - }; - expect(signal).toEqual(expected); - }); - - test('it builds a signal as expected with original_event if is present', () => { - const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - doc._source.event = { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }; - const rule: Partial = sampleRule(); - const signal = buildSignal(doc, rule); - const expected: Signal = { - parent: { - id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', - type: 'event', - index: 'myFakeSignalIndex', - depth: 1, - }, - original_time: 'someTimeStamp', - original_event: { - action: 'socket_opened', - dataset: 'socket', - kind: 'event', - module: 'system', - }, - status: 'open', - rule: { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - }, - }; - expect(signal).toEqual(expected); - }); - }); - - describe('buildRule', () => { - test('it builds a rule as expected with filters present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = [ - { - query: 'host.name: Rebecca', - }, - { - query: 'host.name: Evan', - }, - { - query: 'host.name: Braden', - }, - ]; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: false, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: false, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - filters: [ - { - query: 'host.name: Rebecca', - }, - { - query: 'host.name: Evan', - }, - { - query: 'host.name: Braden', - }, - ], - }; - expect(rule).toEqual(expected); - }); - - test('it omits a null value such as if enabled is null if is present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = undefined; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: true, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - }; - expect(rule).toEqual(expected); - }); - - test('it omits a null value such as if filters is undefined if is present', () => { - const ruleParams = sampleRuleAlertParams(); - ruleParams.filters = undefined; - const rule = buildRule({ - ruleParams, - name: 'some-name', - id: sampleRuleGuid, - enabled: true, - createdBy: 'elastic', - updatedBy: 'elastic', - interval: 'some interval', - tags: ['some fake tag 1', 'some fake tag 2'], - }); - const expected: Partial = { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: 'some interval', - language: 'kuery', - max_signals: 10000, - name: 'some-name', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://google.com'], - risk_score: 50, - rule_id: 'rule-1', - severity: 'high', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - updated_by: 'elastic', - }; - expect(rule).toEqual(expected); - }); - }); -}); 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 deleted file mode 100644 index 1787aa3a3081b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ /dev/null @@ -1,408 +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 { createHash } from 'crypto'; -import { performance } from 'perf_hooks'; -import { pickBy } from 'lodash/fp'; -import { SignalHit, Signal } from '../../types'; -import { Logger } from '../../../../../../../../src/core/server'; -import { AlertServices } from '../../../../../alerting/server/types'; -import { - SignalSourceHit, - SignalSearchResponse, - BulkResponse, - RuleTypeParams, - OutputRuleAlertRest, -} from './types'; -import { buildEventsSearchQuery } from './build_events_query'; - -interface BuildRuleParams { - ruleParams: RuleTypeParams; - name: string; - id: string; - enabled: boolean; - createdBy: string; - updatedBy: string; - interval: string; - tags: string[]; -} - -export const buildRule = ({ - ruleParams, - name, - id, - enabled, - createdBy, - updatedBy, - interval, - tags, -}: BuildRuleParams): Partial => { - return pickBy((value: unknown) => value != null, { - id, - rule_id: ruleParams.ruleId, - false_positives: ruleParams.falsePositives, - saved_id: ruleParams.savedId, - meta: ruleParams.meta, - max_signals: ruleParams.maxSignals, - risk_score: ruleParams.riskScore, - output_index: ruleParams.outputIndex, - description: ruleParams.description, - from: ruleParams.from, - immutable: ruleParams.immutable, - index: ruleParams.index, - interval, - language: ruleParams.language, - name, - query: ruleParams.query, - references: ruleParams.references, - severity: ruleParams.severity, - tags, - type: ruleParams.type, - to: ruleParams.to, - enabled, - filters: ruleParams.filters, - created_by: createdBy, - updated_by: updatedBy, - threats: ruleParams.threats, - }); -}; - -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { - const signal: Signal = { - parent: { - id: doc._id, - type: 'event', - index: doc._index, - depth: 1, - }, - original_time: doc._source['@timestamp'], - status: 'open', - rule, - }; - if (doc._source.event != null) { - return { ...signal, original_event: doc._source.event }; - } - return signal; -}; - -interface BuildBulkBodyParams { - doc: SignalSourceHit; - ruleParams: RuleTypeParams; - id: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - tags: string[]; -} - -export const buildEventTypeSignal = (doc: SignalSourceHit): object => { - if (doc._source.event != null && doc._source.event instanceof Object) { - return { ...doc._source.event, kind: 'signal' }; - } else { - return { kind: 'signal' }; - } -}; - -// format search_after result for signals index. -export const buildBulkBody = ({ - doc, - ruleParams, - id, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, -}: BuildBulkBodyParams): SignalHit => { - const rule = buildRule({ - ruleParams, - id, - name, - enabled, - createdBy, - updatedBy, - interval, - tags, - }); - const signal = buildSignal(doc, rule); - const event = buildEventTypeSignal(doc); - const signalHit: SignalHit = { - ...doc._source, - '@timestamp': new Date().toISOString(), - event, - signal, - }; - return signalHit; -}; - -interface SingleBulkCreateParams { - someResult: SignalSearchResponse; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - id: string; - signalsIndex: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - tags: string[]; -} - -export const generateId = ( - docIndex: string, - docId: string, - version: string, - ruleId: string -): string => - createHash('sha256') - .update(docIndex.concat(docId, version, ruleId)) - .digest('hex'); - -// Bulk Index documents. -export const singleBulkCreate = async ({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, -}: SingleBulkCreateParams): Promise => { - if (someResult.hits.hits.length === 0) { - return true; - } - // index documents after creating an ID based on the - // source documents' originating index, and the original - // document _id. This will allow two documents from two - // different indexes with the same ID to be - // indexed, and prevents us from creating any updates - // to the documents once inserted into the signals index, - // while preventing duplicates from being added to the - // signals index if rules are re-run over the same time - // span. Also allow for versioning. - const bulkBody = someResult.hits.hits.flatMap(doc => [ - { - create: { - _index: signalsIndex, - _id: generateId( - doc._index, - doc._id, - doc._version ? doc._version.toString() : '', - ruleParams.ruleId ?? '' - ), - }, - }, - buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), - ]); - const time1 = performance.now(); - const firstResult: BulkResponse = await services.callCluster('bulk', { - index: signalsIndex, - refresh: false, - body: bulkBody, - }); - const time2 = performance.now(); - 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 - // types of errors they are, count them up, and log them. - const errorCountMap = firstResult.items.reduce((acc: { [key: string]: number }, item) => { - if (item.create.error) { - const responseStatusKey = item.create.status.toString(); - acc[responseStatusKey] = acc[responseStatusKey] ? acc[responseStatusKey] + 1 : 1; - } - return acc; - }, {}); - /* - the logging output below should look like - {'409': 55} - which is read as "there were 55 counts of 409 errors returned from bulk create" - */ - logger.error( - `[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify( - errorCountMap, - null, - 2 - )}` - ); - } - return true; -}; - -interface SingleSearchAfterParams { - searchAfterSortId: string | undefined; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - pageSize: number; - filter: unknown; -} - -// utilize search_after for paging results into bulk. -export const singleSearchAfter = async ({ - searchAfterSortId, - ruleParams, - services, - filter, - logger, - pageSize, -}: SingleSearchAfterParams): Promise => { - if (searchAfterSortId == null) { - throw Error('Attempted to search after with empty sort id'); - } - try { - const searchAfterQuery = buildEventsSearchQuery({ - index: ruleParams.index, - from: ruleParams.from, - to: ruleParams.to, - filter, - size: pageSize, - searchAfterSortId, - }); - const nextSearchAfterResult: SignalSearchResponse = await services.callCluster( - 'search', - searchAfterQuery - ); - return nextSearchAfterResult; - } catch (exc) { - logger.error(`[-] nextSearchAfter threw an error ${exc}`); - throw exc; - } -}; - -interface SearchAfterAndBulkCreateParams { - someResult: SignalSearchResponse; - ruleParams: RuleTypeParams; - services: AlertServices; - logger: Logger; - id: string; - signalsIndex: string; - name: string; - createdBy: string; - updatedBy: string; - interval: string; - enabled: boolean; - pageSize: number; - filter: unknown; - tags: string[]; -} - -// search_after through documents and re-index using bulk endpoint. -export const searchAfterAndBulkCreate = async ({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - filter, - name, - createdBy, - updatedBy, - interval, - enabled, - pageSize, - tags, -}: SearchAfterAndBulkCreateParams): Promise => { - if (someResult.hits.hits.length === 0) { - return true; - } - - logger.debug('[+] starting bulk insertion'); - await singleBulkCreate({ - someResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, - }); - const totalHits = - typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; - // maxTotalHitsSize represents the total number of docs to - // query for, no matter the size of each individual page of search results. - // If the total number of hits for the overall search result is greater than - // maxSignals, default to requesting a total of maxSignals, otherwise use the - // totalHits in the response from the searchAfter query. - const maxTotalHitsSize = totalHits >= ruleParams.maxSignals ? ruleParams.maxSignals : totalHits; - - // number of docs in the current search result - let hitsSize = someResult.hits.hits.length; - logger.debug(`first size: ${hitsSize}`); - let sortIds = someResult.hits.hits[0].sort; - if (sortIds == null && totalHits > 0) { - logger.error('sortIds was empty on first search but expected more'); - return false; - } else if (sortIds == null && totalHits === 0) { - return true; - } - let sortId; - if (sortIds != null) { - sortId = sortIds[0]; - } - while (hitsSize < maxTotalHitsSize && hitsSize !== 0) { - try { - logger.debug(`sortIds: ${sortIds}`); - const searchAfterResult: SignalSearchResponse = await singleSearchAfter({ - searchAfterSortId: sortId, - ruleParams, - services, - logger, - filter, - pageSize, // maximum number of docs to receive per search result. - }); - if (searchAfterResult.hits.hits.length === 0) { - return true; - } - hitsSize += searchAfterResult.hits.hits.length; - logger.debug(`size adjusted: ${hitsSize}`); - sortIds = searchAfterResult.hits.hits[0].sort; - if (sortIds == null) { - logger.debug('sortIds was empty on search'); - return true; // no more search results - } - sortId = sortIds[0]; - logger.debug('next bulk index'); - await singleBulkCreate({ - someResult: searchAfterResult, - ruleParams, - services, - logger, - id, - signalsIndex, - name, - createdBy, - updatedBy, - interval, - enabled, - tags, - }); - logger.debug('finished next bulk index'); - } catch (exc) { - logger.error(`[-] search_after and bulk threw an error ${exc}`); - return false; - } - } - logger.debug(`[+] completed bulk index of ${maxTotalHitsSize}`); - return true; -}; 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 cd8b716221b9b..978434859ef95 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 @@ -6,11 +6,13 @@ import { ServerInjectOptions } from 'hapi'; import { ActionResult } from '../../../../../../actions/server/types'; -import { RuleAlertParamsRest, RuleAlertType, SignalsRestParams } from '../../alerts/types'; +import { SignalsRestParams } from '../../signals/types'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, } from '../../../../../common/constants'; +import { RuleAlertType } from '../../rules/types'; +import { RuleAlertParamsRest } from '../../types'; // The Omit of filter is because of a Hapi Server Typing issue that I am unclear // where it comes from. I would hope to remove the "filter" as an omit at some point 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/rules/create_rules_route.test.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index b271af2db1e7d..094449a5f61ac 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/rules/create_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; import { ServerInjectOptions } from 'hapi'; import { @@ -18,8 +18,8 @@ import { createActionResult, getCreateRequest, typicalPayload, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('create_rules', () => { let { server, alertsClient, actionsClient, elasticsearch } = createMockServer(); 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/rules/create_rules_route.ts similarity index 85% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index a137d54250189..0dc213e9e2173 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/rules/create_rules_route.ts @@ -8,14 +8,15 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; import Boom from 'boom'; import uuid from 'uuid'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { createRules } from '../alerts/create_rules'; -import { RulesRequest } from '../alerts/types'; -import { createRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { readRules } from '../alerts/read_rules'; -import { transformOrError, transformError, getIndex, callWithRequestFactory } from './utils'; -import { getIndexExists } from '../index/get_index_exists'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { createRules } from '../../rules/create_rules'; +import { RulesRequest } from '../../rules/types'; +import { createRulesSchema } from '../schemas/create_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { readRules } from '../../rules/read_rules'; +import { transformOrError } from './utils'; +import { getIndexExists } from '../../index/get_index_exists'; +import { callWithRequestFactory, getIndex, transformError } from '../utils'; export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index 0808051964dc1..cacafcf741e6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { deleteRulesRoute } from './delete_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -19,8 +19,8 @@ import { getDeleteRequest, getFindResultWithSingleHit, getDeleteRequestById, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('delete_rules', () => { let { server, alertsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts index fe8b139f11c01..c2b2e2fdbbaef 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/delete_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -7,12 +7,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { deleteRules } from '../alerts/delete_rules'; -import { ServerFacade } from '../../../types'; -import { queryRulesSchema } from './schemas'; -import { QueryRequest } from '../alerts/types'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { deleteRules } from '../../rules/delete_rules'; +import { ServerFacade } from '../../../../types'; +import { queryRulesSchema } from '../schemas/query_rules_schema'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; +import { QueryRequest } from './types'; export const createDeleteRulesRoute: Hapi.ServerRoute = { method: 'DELETE', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index dae40f05155dc..38937c13d302c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -9,12 +9,12 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { findRulesRoute } from './find_rules_route'; import { ServerInjectOptions } from 'hapi'; -import { getFindResult, getResult, getFindRequest } from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +import { getFindResult, getResult, getFindRequest } from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('find_rules', () => { let { server, alertsClient, actionsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts index 137dd9352699e..6e89ddb19017d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/find_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -6,12 +6,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { findRules } from '../alerts/find_rules'; -import { FindRulesRequest } from '../alerts/types'; -import { findRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { transformFindAlertsOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { findRules } from '../../rules/find_rules'; +import { FindRulesRequest } from '../../rules/types'; +import { findRulesSchema } from '../schemas/find_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { transformFindAlertsOrError } from './utils'; +import { transformError } from '../utils'; export const createFindRulesRoute: Hapi.ServerRoute = { method: 'GET', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 47ecf62f41be9..0d77583573c13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { readRulesRoute } from './read_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -18,8 +18,8 @@ import { getResult, getReadRequest, getFindResultWithSingleHit, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('read_signals', () => { let { server, alertsClient } = createMockServer(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts similarity index 78% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts index a7bda40fdc523..a842e68b6b7fe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/read_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -6,13 +6,14 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; -import { readRules } from '../alerts/read_rules'; -import { ServerFacade } from '../../../types'; -import { queryRulesSchema } from './schemas'; -import { QueryRequest } from '../alerts/types'; +import { readRules } from '../../rules/read_rules'; +import { ServerFacade } from '../../../../types'; +import { queryRulesSchema } from '../schemas/query_rules_schema'; +import { QueryRequest } from './types'; export const createReadRulesRoute: Hapi.ServerRoute = { method: 'GET', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts new file mode 100644 index 0000000000000..f6878c9edc9b8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/types.ts @@ -0,0 +1,11 @@ +/* + * 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 { RequestFacade } from '../../../../types'; + +export type QueryRequest = Omit & { + query: { id: string | undefined; rule_id: string | undefined }; +}; 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/rules/update_rules_route.test.ts similarity index 97% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index dfa1275a6b26b..3cf5c07655d92 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/rules/update_rules_route.test.ts @@ -9,7 +9,7 @@ import { createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, createMockServerWithoutActionOrAlertClientDecoration, -} from './__mocks__/_mock_server'; +} from '../__mocks__/_mock_server'; import { updateRulesRoute } from './update_rules_route'; import { ServerInjectOptions } from 'hapi'; @@ -20,8 +20,8 @@ import { getUpdateRequest, typicalPayload, getFindResultWithSingleHit, -} from './__mocks__/request_responses'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +} from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; describe('update_rules', () => { let { server, alertsClient, actionsClient } = createMockServer(); 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/rules/update_rules_route.ts similarity index 84% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_rules_route.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 943c41fd6dea6..2e7b48afbb5d9 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/rules/update_rules_route.ts @@ -6,12 +6,13 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; -import { updateRules } from '../alerts/update_rules'; -import { UpdateRulesRequest } from '../alerts/types'; -import { updateRulesSchema } from './schemas'; -import { ServerFacade } from '../../../types'; -import { getIdError, transformOrError, transformError } from './utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { updateRules } from '../../rules/update_rules'; +import { UpdateRulesRequest } from '../../rules/types'; +import { updateRulesSchema } from '../schemas/update_rules_schema'; +import { ServerFacade } from '../../../../types'; +import { getIdError, transformOrError } from './utils'; +import { transformError } from '../utils'; export const createUpdateRulesRoute: Hapi.ServerRoute = { method: 'PUT', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts new file mode 100644 index 0000000000000..d4e129f543ccf --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -0,0 +1,496 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; + +import { + transformAlertToRule, + getIdError, + transformFindAlertsOrError, + transformOrError, +} from './utils'; +import { getResult } from '../__mocks__/request_responses'; + +describe('utils', () => { + describe('transformAlertToRule', () => { + test('should work with a full data set', () => { + const fullRule = getResult(); + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should work with a partial data set missing data', () => { + const fullRule = getResult(); + const { from, language, ...omitData } = transformAlertToRule(fullRule); + expect(omitData).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit query if query is null', () => { + const fullRule = getResult(); + fullRule.params.query = null; + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit query if query is undefined', () => { + const fullRule = getResult(); + fullRule.params.query = undefined; + const rule = transformAlertToRule(fullRule); + expect(rule).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + output_index: '.siem-signals', + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should omit a mix of undefined, null, and missing fields', () => { + const fullRule = getResult(); + fullRule.params.query = undefined; + fullRule.params.language = null; + const { from, enabled, ...omitData } = transformAlertToRule(fullRule); + expect(omitData).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + max_signals: 100, + name: 'Detect Root/Admin Users', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should return enabled is equal to false', () => { + const fullRule = getResult(); + fullRule.enabled = false; + const ruleWithEnabledFalse = transformAlertToRule(fullRule); + expect(ruleWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + + test('should return immutable is equal to false', () => { + const fullRule = getResult(); + fullRule.params.immutable = false; + const ruleWithEnabledFalse = transformAlertToRule(fullRule); + expect(ruleWithEnabledFalse).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + from: 'now-6m', + false_positives: [], + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + risk_score: 50, + rule_id: 'rule-1', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + 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/', + }, + ], + }, + ], + to: 'now', + type: 'query', + }); + }); + }); + + 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: "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: "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: "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: "rule-id-123" not found'); + }); + + test('outputs message about both being not defined when both are undefined', () => { + const boom = getIdError({ id: undefined, ruleId: undefined }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when both are null', () => { + const boom = getIdError({ id: null, ruleId: null }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when id is null and ruleId is undefined', () => { + const boom = getIdError({ id: null, ruleId: undefined }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + + test('outputs message about both being not defined when id is undefined and ruleId is null', () => { + const boom = getIdError({ id: undefined, ruleId: null }); + expect(boom.message).toEqual('id or rule_id should have been defined'); + }); + }); + + describe('transformFindAlertsOrError', () => { + test('outputs empty data set when data set is empty correct', () => { + const output = transformFindAlertsOrError({ data: [] }); + expect(output).toEqual({ data: [] }); + }); + + test('outputs 200 if the data is of type siem alert', () => { + const output = transformFindAlertsOrError({ + data: [getResult()], + }); + expect(output).toEqual({ + data: [ + { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + 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/', + }, + ], + }, + ], + }, + ], + }); + }); + + test('returns 500 if the data is not of type siem alert', () => { + const output = transformFindAlertsOrError({ data: [{ random: 1 }] }); + expect((output as Boom).message).toEqual('Internal error transforming'); + }); + }); + + describe('transformOrError', () => { + test('outputs 200 if the data is of type siem alert', () => { + const output = transformOrError(getResult()); + expect(output).toEqual({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + output_index: '.siem-signals', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + risk_score: 50, + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + 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/', + }, + ], + }, + ], + }); + }); + + test('returns 500 if the data is not of type siem alert', () => { + const output = transformOrError({ data: [{ random: 1 }] }); + expect((output as Boom).message).toEqual('Internal error transforming'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts new file mode 100644 index 0000000000000..c9ae3abdfdc6b --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { pickBy } from 'lodash/fp'; +import { RuleAlertType, isAlertType, isAlertTypes } from '../../rules/types'; +import { OutputRuleAlertRest } from '../../types'; + +export const getIdError = ({ + id, + ruleId, +}: { + id: string | undefined | null; + ruleId: string | undefined | null; +}) => { + if (id != null) { + return new Boom(`id: "${id}" not found`, { statusCode: 404 }); + } else if (ruleId != null) { + return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); + } else { + return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); + } +}; + +// Transforms the data but will remove any null or undefined it encounters and not include +// those on the export +export const transformAlertToRule = (alert: RuleAlertType): Partial => { + return pickBy((value: unknown) => value != null, { + created_by: alert.createdBy, + description: alert.params.description, + enabled: alert.enabled, + false_positives: alert.params.falsePositives, + filters: alert.params.filters, + from: alert.params.from, + id: alert.id, + immutable: alert.params.immutable, + index: alert.params.index, + interval: alert.interval, + rule_id: alert.params.ruleId, + language: alert.params.language, + output_index: alert.params.outputIndex, + max_signals: alert.params.maxSignals, + risk_score: alert.params.riskScore, + name: alert.name, + query: alert.params.query, + references: alert.params.references, + saved_id: alert.params.savedId, + meta: alert.params.meta, + severity: alert.params.severity, + updated_by: alert.updatedBy, + tags: alert.tags, + to: alert.params.to, + type: alert.params.type, + threats: alert.params.threats, + }); +}; + +export const transformFindAlertsOrError = (findResults: { data: unknown[] }): unknown | Boom => { + if (isAlertTypes(findResults.data)) { + findResults.data = findResults.data.map(alert => transformAlertToRule(alert)); + return findResults; + } else { + return new Boom('Internal error transforming', { statusCode: 500 }); + } +}; + +export const transformOrError = (alert: unknown): Partial | Boom => { + if (isAlertType(alert)) { + return transformAlertToRule(alert); + } else { + return new Boom('Internal error transforming', { statusCode: 500 }); + } +}; 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 deleted file mode 100644 index f5147bc5a8f8b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ /dev/null @@ -1,2133 +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 { - createRulesSchema, - updateRulesSchema, - findRulesSchema, - queryRulesSchema, - setSignalsStatusSchema, -} from './schemas'; -import { - RuleAlertParamsRest, - FindParamsRest, - UpdateRuleAlertParamsRest, - ThreatParams, - SignalsRestParams, -} from '../alerts/types'; - -describe('schemas', () => { - describe('create rules schema', () => { - test('empty objects do not validate', () => { - expect(createRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - createRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[rule_id] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - interval: '5m', - index: ['index-1'], - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - 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', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - risk_score: 50, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - 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', - }).error - ).toBeFalsy(); - }); - test('You can send in an empty array to threats', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).error - ).toBeFalsy(); - }); - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { - 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', - threats: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeFalsy(); - }); - - test('allows references to be sent as valid', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('defaults references to an array', () => { - 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', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual([]); - }); - - test('references cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { references: number[] } - >({ - 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', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { index: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('defaults interval to 5 min', () => { - 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', - type: 'query', - }).value.interval - ).toEqual('5m'); - }); - - test('defaults max signals to 100', () => { - 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', - }).value.max_signals - ).toEqual(100); - }); - - test('saved_id is required when type is saved_query and will not validate without out', () => { - 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: 'saved_query', - }).error - ).toBeTruthy(); - }); - - test('saved_id is required when type is saved_query and validates 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', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters with it', () => { - 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: 'saved_query', - saved_id: 'some id', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('filters cannot be a string', () => { - expect( - createRulesSchema.validate< - Partial & { filters: string }> - >({ - 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: 'saved_query', - saved_id: 'some id', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('language validates with kuery', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - 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: 'query', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can optionally send in an array of tags', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: ['tag_1', 'tag_2'], - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of tags that are numbers', () => { - expect( - createRulesSchema.validate> & { tags: number[] }>( - { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: [0, 1, 2], - } - ).error - ).toBeTruthy(); - }); - - test('You cannot send in an array of threats that are missing "framework"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "tactic"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "techniques"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - - test('You can optionally send in an array of false positives', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: ['false_1', 'false_2'], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of false positives that are numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { false_positives: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: [5, 4], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can optionally set the immutable to be true', () => { - expect( - createRulesSchema.validate>({ - 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: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot set the immutable to be a number', () => { - expect( - createRulesSchema.validate< - Partial> & { immutable: number } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: 5, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to 101', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 101, - 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: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to -1', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: -1, - 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: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can set the risk_score to 0', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 0, - 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: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set the risk_score to 100', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 100, - 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: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set meta to any object you want', () => { - expect( - createRulesSchema.validate>({ - 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: 'some query', - language: 'kuery', - max_signals: 1, - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }).error - ).toBeFalsy(); - }); - - test('You cannot create meta as a string', () => { - 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: 'some query', - language: 'kuery', - max_signals: 1, - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('You can omit the 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'], - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - }); - - describe('update rules schema', () => { - test('empty objects do not validate as they require at least id or rule_id', () => { - expect(updateRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - updateRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[id] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[rule_id] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[id and rule_id] does not validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'id-1', - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, risk_score] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - risk_score: 10, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { - 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', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - 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', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - 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', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { - 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', - }).error - ).toBeFalsy(); - }); - - test('allows references to be sent as a valid value to update with', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('does not default references to an array', () => { - 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', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual(undefined); - }); - - test('does not default interval', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - type: 'query', - }).value.interval - ).toEqual(undefined); - }); - - test('does not default max signal', () => { - 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', - }).value.max_signals - ).toEqual(undefined); - }); - - test('references cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { references: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { index: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('saved_id is not required when type is saved_query and will validate without 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', - }).error - ).toBeFalsy(); - }); - - test('saved_id validates with saved_query', () => { - 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', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters 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', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('language validates with kuery', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('meta can be updated', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - meta: { whateverYouWant: 'anything_at_all' }, - }).error - ).toBeFalsy(); - }); - - test('You update meta as a string', () => { - expect( - updateRulesSchema.validate< - Partial & { meta: string }> - >({ - id: 'rule-1', - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('filters cannot be a string', () => { - expect( - updateRulesSchema.validate< - Partial & { filters: string }> - >({ - rule_id: 'rule-1', - type: 'query', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('threats is not defaulted to empty array on update', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).value.threats - ).toBe(undefined); - }); - - test('threats is not defaulted to undefined on update with empty array', () => { - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).value.threats - ).toMatchObject([]); - }); - test('threats is valid when updated with all sub-objects', () => { - const expected: ThreatParams[] = [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ]; - 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', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).value.threats - ).toMatchObject(expected); - }); - test('threats is invalid when updated with missing property framework', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing tactic sub-object', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing techniques', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - }); - - describe('find rules schema', () => { - test('empty objects do validate', () => { - expect(findRulesSchema.validate>({}).error).toBeFalsy(); - }); - - test('all values validate', () => { - expect( - findRulesSchema.validate>({ - per_page: 5, - page: 1, - sort_field: 'some field', - fields: ['field 1', 'field 2'], - filter: 'some filter', - sort_order: 'asc', - }).error - ).toBeFalsy(); - }); - - test('made up parameters do not validate', () => { - expect( - findRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('per_page validates', () => { - expect( - findRulesSchema.validate>({ per_page: 5 }).error - ).toBeFalsy(); - }); - - test('page validates', () => { - expect( - findRulesSchema.validate>({ page: 5 }).error - ).toBeFalsy(); - }); - - test('sort_field validates', () => { - expect( - findRulesSchema.validate>({ sort_field: 'some value' }).error - ).toBeFalsy(); - }); - - test('fields validates with a string', () => { - expect( - findRulesSchema.validate>({ fields: ['some value'] }).error - ).toBeFalsy(); - }); - - test('fields validates with multiple strings', () => { - expect( - findRulesSchema.validate>({ - fields: ['some value 1', 'some value 2'], - }).error - ).toBeFalsy(); - }); - - test('fields does not validate with a number', () => { - expect( - findRulesSchema.validate> & { fields: number[] }>({ - fields: [5], - }).error - ).toBeTruthy(); - }); - - test('per page has a default of 20', () => { - expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); - }); - - test('page has a default of 1', () => { - expect(findRulesSchema.validate>({}).value.page).toEqual(1); - }); - - test('filter works with a string', () => { - expect( - findRulesSchema.validate>({ - filter: 'some value 1', - }).error - ).toBeFalsy(); - }); - - test('filter does not work with a number', () => { - expect( - findRulesSchema.validate> & { filter: number }>({ - filter: 5, - }).error - ).toBeTruthy(); - }); - - test('sort_order requires sort_field to work', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - }).error - ).toBeTruthy(); - }); - - test('sort_order and sort_field validate together', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order validates with desc and sort_field', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'desc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order does not validate with a string other than asc and desc', () => { - expect( - findRulesSchema.validate< - Partial> & { sort_order: string } - >({ - sort_order: 'some other string', - sort_field: 'some field', - }).error - ).toBeTruthy(); - }); - }); - - describe('queryRulesSchema', () => { - test('empty objects do not validate', () => { - expect(queryRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('both rule_id and id being supplied dot not validate', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1', id: '1' }) - .error - ).toBeTruthy(); - }); - - test('only id validates', () => { - expect( - queryRulesSchema.validate>({ id: '1' }).error - ).toBeFalsy(); - }); - - test('only rule_id validates', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1' }).error - ).toBeFalsy(); - }); - }); - - describe('set signal status schema', () => { - test('signal_ids and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('query and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('signal_ids and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - }).error - ).toBeTruthy(); - }); - - test('query and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - }).error - ).toBeTruthy(); - }); - - test('status is present but query or signal_ids is missing is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - status: 'closed', - }).error - ).toBeTruthy(); - }); - - test('signal_ids is present but status has wrong value', () => { - expect( - setSignalsStatusSchema.validate< - Partial< - Omit & { - status: string; - } - > - >({ - status: 'fakeVal', - }).error - ).toBeTruthy(); - }); - }); -}); 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 deleted file mode 100644 index 6ed6fdd2577d8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ /dev/null @@ -1,162 +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 { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; - -/* eslint-disable @typescript-eslint/camelcase */ -const description = Joi.string(); -const enabled = Joi.boolean(); -const false_positives = Joi.array().items(Joi.string()); -const filters = Joi.array(); -const from = Joi.string(); -const immutable = Joi.boolean(); -const rule_id = Joi.string(); -const id = Joi.string(); -const index = Joi.array() - .items(Joi.string()) - .single(); -const interval = Joi.string(); -const query = Joi.string(); -const language = Joi.string().valid('kuery', 'lucene'); -const output_index = Joi.string(); -const saved_id = Joi.string(); -const meta = Joi.object(); -const max_signals = Joi.number().greater(0); -const name = Joi.string(); -const risk_score = Joi.number() - .greater(-1) - .less(101); -const severity = Joi.string(); -const status = Joi.string().valid('open', 'closed'); -const to = Joi.string(); -const type = Joi.string().valid('query', 'saved_query'); -const queryFilter = Joi.string(); -const references = Joi.array() - .items(Joi.string()) - .single(); -const per_page = Joi.number() - .min(0) - .default(20); -const page = Joi.number() - .min(1) - .default(1); -const signal_ids = Joi.array().items(Joi.string()); -const signal_status_query = Joi.object(); -const sort_field = Joi.string(); -const sort_order = Joi.string().valid('asc', 'desc'); -const tags = Joi.array().items(Joi.string()); -const fields = Joi.array() - .items(Joi.string()) - .single(); -const threat_framework = Joi.string(); -const threat_tactic_id = Joi.string(); -const threat_tactic_name = Joi.string(); -const threat_tactic_reference = Joi.string(); -const threat_tactic = Joi.object({ - id: threat_tactic_id.required(), - name: threat_tactic_name.required(), - reference: threat_tactic_reference.required(), -}); -const threat_technique_id = Joi.string(); -const threat_technique_name = Joi.string(); -const threat_technique_reference = Joi.string(); -const threat_technique = Joi.object({ - id: threat_technique_id.required(), - name: threat_technique_name.required(), - reference: threat_technique_reference.required(), -}); -const threat_techniques = Joi.array().items(threat_technique.required()); - -const threats = Joi.array().items( - Joi.object({ - framework: threat_framework.required(), - tactic: threat_tactic.required(), - techniques: threat_techniques.required(), - }) -); -/* eslint-enable @typescript-eslint/camelcase */ - -export const createRulesSchema = Joi.object({ - description: description.required(), - enabled: enabled.default(true), - false_positives: false_positives.default([]), - filters, - from: from.required(), - rule_id, - immutable: immutable.default(false), - index, - interval: interval.default('5m'), - query: query.allow('').default(''), - language: language.default('kuery'), - output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.required(), - otherwise: Joi.forbidden(), - }), - meta, - risk_score: risk_score.required(), - max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), - name: name.required(), - severity: severity.required(), - tags: tags.default([]), - to: to.required(), - type: type.required(), - threats: threats.default([]), - references: references.default([]), -}); - -export const updateRulesSchema = Joi.object({ - description, - enabled, - false_positives, - filters, - from, - rule_id, - id, - immutable, - index, - interval, - query: query.allow(''), - language, - output_index, - saved_id, - meta, - risk_score, - max_signals, - name, - severity, - tags, - to, - type, - threats, - references, -}).xor('id', 'rule_id'); - -export const queryRulesSchema = Joi.object({ - rule_id, - id, -}).xor('id', 'rule_id'); - -export const findRulesSchema = Joi.object({ - fields, - filter: queryFilter, - per_page, - page, - sort_field: Joi.when(Joi.ref('sort_order'), { - is: Joi.exist(), - then: sort_field.required(), - otherwise: sort_field.optional(), - }), - sort_order, -}); - -export const setSignalsStatusSchema = Joi.object({ - signal_ids, - query: signal_status_query, - status: status.required(), -}).xor('signal_ids', 'query'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts new file mode 100644 index 0000000000000..4efea69db1f41 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -0,0 +1,1047 @@ +/* + * 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 { createRulesSchema } from './create_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; +import { ThreatParams, RuleAlertParamsRest } from '../../types'; + +describe('create rules schema', () => { + test('empty objects do not validate', () => { + expect(createRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + createRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[rule_id] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + interval: '5m', + index: ['index-1'], + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { + 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', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + risk_score: 50, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { + 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', + }).error + ).toBeFalsy(); + }); + test('You can send in an empty array to threats', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).error + ).toBeFalsy(); + }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { + 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', + threats: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeFalsy(); + }); + + test('allows references to be sent as valid', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('defaults references to an array', () => { + 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', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual([]); + }); + + test('references cannot be numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { references: number[] } + >({ + 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', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + createRulesSchema.validate> & { index: number[] }>( + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + } + ).error + ).toBeTruthy(); + }); + + test('defaults interval to 5 min', () => { + 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', + type: 'query', + }).value.interval + ).toEqual('5m'); + }); + + test('defaults max signals to 100', () => { + 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', + }).value.max_signals + ).toEqual(100); + }); + + test('saved_id is required when type is saved_query and will not validate without out', () => { + 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: 'saved_query', + }).error + ).toBeTruthy(); + }); + + test('saved_id is required when type is saved_query and validates 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', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + 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: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('filters cannot be a string', () => { + expect( + createRulesSchema.validate< + Partial & { filters: string }> + >({ + 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: 'saved_query', + saved_id: 'some id', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('language validates with kuery', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + 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: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can optionally send in an array of tags', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: ['tag_1', 'tag_2'], + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of tags that are numbers', () => { + expect( + createRulesSchema.validate> & { tags: number[] }>({ + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: [0, 1, 2], + }).error + ).toBeTruthy(); + }); + + test('You cannot send in an array of threats that are missing "framework"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "tactic"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "techniques"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + + test('You can optionally send in an array of false positives', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: ['false_1', 'false_2'], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of false positives that are numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { false_positives: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: [5, 4], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can optionally set the immutable to be true', () => { + expect( + createRulesSchema.validate>({ + 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: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot set the immutable to be a number', () => { + expect( + createRulesSchema.validate< + Partial> & { immutable: number } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: 5, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to 101', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 101, + 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: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to -1', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: -1, + 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: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can set the risk_score to 0', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 0, + 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: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set the risk_score to 100', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 100, + 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: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set meta to any object you want', () => { + expect( + createRulesSchema.validate>({ + 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: 'some query', + language: 'kuery', + max_signals: 1, + meta: { + somethingMadeUp: { somethingElse: true }, + }, + }).error + ).toBeFalsy(); + }); + + test('You cannot create meta as a string', () => { + 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: 'some query', + language: 'kuery', + max_signals: 1, + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('You can omit the 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'], + language: 'kuery', + filters: [], + max_signals: 1, + }).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts new file mode 100644 index 0000000000000..ccda7256d2eeb --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -0,0 +1,67 @@ +/* + * 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'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { + enabled, + description, + false_positives, + filters, + from, + immutable, + index, + rule_id, + interval, + query, + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, +} from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; + +export const createRulesSchema = Joi.object({ + description: description.required(), + enabled: enabled.default(true), + false_positives: false_positives.default([]), + filters, + from: from.required(), + rule_id, + immutable: immutable.default(false), + index, + interval: interval.default('5m'), + query: query.allow('').default(''), + language: language.default('kuery'), + output_index, + saved_id: saved_id.when('type', { + is: 'saved_query', + then: Joi.required(), + otherwise: Joi.forbidden(), + }), + meta, + risk_score: risk_score.required(), + max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), + name: name.required(), + severity: severity.required(), + tags: tags.default([]), + to: to.required(), + type: type.required(), + threats: threats.default([]), + references: references.default([]), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts new file mode 100644 index 0000000000000..14b3bdb298739 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.test.ts @@ -0,0 +1,136 @@ +/* + * 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 { findRulesSchema } from './find_rules_schema'; +import { FindParamsRest } from '../../rules/types'; + +describe('find rules schema', () => { + test('empty objects do validate', () => { + expect(findRulesSchema.validate>({}).error).toBeFalsy(); + }); + + test('all values validate', () => { + expect( + findRulesSchema.validate>({ + per_page: 5, + page: 1, + sort_field: 'some field', + fields: ['field 1', 'field 2'], + filter: 'some filter', + sort_order: 'asc', + }).error + ).toBeFalsy(); + }); + + test('made up parameters do not validate', () => { + expect( + findRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('per_page validates', () => { + expect( + findRulesSchema.validate>({ per_page: 5 }).error + ).toBeFalsy(); + }); + + test('page validates', () => { + expect( + findRulesSchema.validate>({ page: 5 }).error + ).toBeFalsy(); + }); + + test('sort_field validates', () => { + expect( + findRulesSchema.validate>({ sort_field: 'some value' }).error + ).toBeFalsy(); + }); + + test('fields validates with a string', () => { + expect( + findRulesSchema.validate>({ fields: ['some value'] }).error + ).toBeFalsy(); + }); + + test('fields validates with multiple strings', () => { + expect( + findRulesSchema.validate>({ + fields: ['some value 1', 'some value 2'], + }).error + ).toBeFalsy(); + }); + + test('fields does not validate with a number', () => { + expect( + findRulesSchema.validate> & { fields: number[] }>({ + fields: [5], + }).error + ).toBeTruthy(); + }); + + test('per page has a default of 20', () => { + expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); + }); + + test('page has a default of 1', () => { + expect(findRulesSchema.validate>({}).value.page).toEqual(1); + }); + + test('filter works with a string', () => { + expect( + findRulesSchema.validate>({ + filter: 'some value 1', + }).error + ).toBeFalsy(); + }); + + test('filter does not work with a number', () => { + expect( + findRulesSchema.validate> & { filter: number }>({ + filter: 5, + }).error + ).toBeTruthy(); + }); + + test('sort_order requires sort_field to work', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + }).error + ).toBeTruthy(); + }); + + test('sort_order and sort_field validate together', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order validates with desc and sort_field', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'desc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order does not validate with a string other than asc and desc', () => { + expect( + findRulesSchema.validate< + Partial> & { sort_order: string } + >({ + sort_order: 'some other string', + sort_field: 'some field', + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.ts new file mode 100644 index 0000000000000..3cc5b9ca44530 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/find_rules_schema.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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { queryFilter, fields, per_page, page, sort_field, sort_order } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const findRulesSchema = Joi.object({ + fields, + filter: queryFilter, + per_page, + page, + sort_field: Joi.when(Joi.ref('sort_order'), { + is: Joi.exist(), + then: sort_field.required(), + otherwise: sort_field.optional(), + }), + sort_order, +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.ts new file mode 100644 index 0000000000000..6c4e96abd2b98 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.test.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 { queryRulesSchema } from './query_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; + +describe('queryRulesSchema', () => { + test('empty objects do not validate', () => { + expect(queryRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('both rule_id and id being supplied dot not validate', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1', id: '1' }).error + ).toBeTruthy(); + }); + + test('only id validates', () => { + expect( + queryRulesSchema.validate>({ id: '1' }).error + ).toBeFalsy(); + }); + + test('only rule_id validates', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1' }).error + ).toBeFalsy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts new file mode 100644 index 0000000000000..86a731699d1ea --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_rules_schema.ts @@ -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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { rule_id, id } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const queryRulesSchema = Joi.object({ + rule_id, + id, +}).xor('id', 'rule_id'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts new file mode 100644 index 0000000000000..5ab8ea3b8af3e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -0,0 +1,79 @@ +/* + * 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'; + +/* eslint-disable @typescript-eslint/camelcase */ +export const description = Joi.string(); +export const enabled = Joi.boolean(); +export const false_positives = Joi.array().items(Joi.string()); +export const filters = Joi.array(); +export const from = Joi.string(); +export const immutable = Joi.boolean(); +export const rule_id = Joi.string(); +export const id = Joi.string(); +export const index = Joi.array() + .items(Joi.string()) + .single(); +export const interval = Joi.string(); +export const query = Joi.string(); +export const language = Joi.string().valid('kuery', 'lucene'); +export const output_index = Joi.string(); +export const saved_id = Joi.string(); +export const meta = Joi.object(); +export const max_signals = Joi.number().greater(0); +export const name = Joi.string(); +export const risk_score = Joi.number() + .greater(-1) + .less(101); +export const severity = Joi.string(); +export const status = Joi.string().valid('open', 'closed'); +export const to = Joi.string(); +export const type = Joi.string().valid('query', 'saved_query'); +export const queryFilter = Joi.string(); +export const references = Joi.array() + .items(Joi.string()) + .single(); +export const per_page = Joi.number() + .min(0) + .default(20); +export const page = Joi.number() + .min(1) + .default(1); +export const signal_ids = Joi.array().items(Joi.string()); +export const signal_status_query = Joi.object(); +export const sort_field = Joi.string(); +export const sort_order = Joi.string().valid('asc', 'desc'); +export const tags = Joi.array().items(Joi.string()); +export const fields = Joi.array() + .items(Joi.string()) + .single(); +export const threat_framework = Joi.string(); +export const threat_tactic_id = Joi.string(); +export const threat_tactic_name = Joi.string(); +export const threat_tactic_reference = Joi.string(); +export const threat_tactic = Joi.object({ + id: threat_tactic_id.required(), + name: threat_tactic_name.required(), + reference: threat_tactic_reference.required(), +}); +export const threat_technique_id = Joi.string(); +export const threat_technique_name = Joi.string(); +export const threat_technique_reference = Joi.string(); +export const threat_technique = Joi.object({ + id: threat_technique_id.required(), + name: threat_technique_name.required(), + reference: threat_technique_reference.required(), +}); +export const threat_techniques = Joi.array().items(threat_technique.required()); + +export const threats = Joi.array().items( + Joi.object({ + framework: threat_framework.required(), + tactic: threat_tactic.required(), + techniques: threat_techniques.required(), + }) +); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts new file mode 100644 index 0000000000000..b586b4666bfee --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { setSignalsStatusSchema } from './set_signal_status_schema'; +import { SignalsRestParams } from '../../signals/types'; + +describe('set signal status schema', () => { + test('signal_ids and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('query and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('signal_ids and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + }).error + ).toBeTruthy(); + }); + + test('query and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + }).error + ).toBeTruthy(); + }); + + test('status is present but query or signal_ids is missing is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + status: 'closed', + }).error + ).toBeTruthy(); + }); + + test('signal_ids is present but status has wrong value', () => { + expect( + setSignalsStatusSchema.validate< + Partial< + Omit & { + status: string; + } + > + >({ + status: 'fakeVal', + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.ts new file mode 100644 index 0000000000000..c8a06619287df --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.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 Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { signal_ids, signal_status_query, status } from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const setSignalsStatusSchema = Joi.object({ + signal_ids, + query: signal_status_query, + status: status.required(), +}).xor('signal_ids', 'query'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts new file mode 100644 index 0000000000000..606a30309b2ab --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -0,0 +1,869 @@ +/* + * 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 { updateRulesSchema } from './update_rules_schema'; +import { UpdateRuleAlertParamsRest } from '../../rules/types'; +import { ThreatParams } from '../../types'; + +describe('update rules schema', () => { + test('empty objects do not validate as they require at least id or rule_id', () => { + expect(updateRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + updateRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[id] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[rule_id] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[id and rule_id] does not validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'id-1', + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, risk_score] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + risk_score: 10, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { + 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', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + 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', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + 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', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { + 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', + }).error + ).toBeFalsy(); + }); + + test('allows references to be sent as a valid value to update with', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('does not default references to an array', () => { + 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', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual(undefined); + }); + + test('does not default interval', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual(undefined); + }); + + test('does not default max signal', () => { + 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', + }).value.max_signals + ).toEqual(undefined); + }); + + test('references cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { references: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { index: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('saved_id is not required when type is saved_query and will validate without 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', + }).error + ).toBeFalsy(); + }); + + test('saved_id validates with saved_query', () => { + 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', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters 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', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('language validates with kuery', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('meta can be updated', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + meta: { whateverYouWant: 'anything_at_all' }, + }).error + ).toBeFalsy(); + }); + + test('You update meta as a string', () => { + expect( + updateRulesSchema.validate< + Partial & { meta: string }> + >({ + id: 'rule-1', + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('filters cannot be a string', () => { + expect( + updateRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + type: 'query', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('threats is not defaulted to empty array on update', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).value.threats + ).toBe(undefined); + }); + + test('threats is not defaulted to undefined on update with empty array', () => { + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).value.threats + ).toMatchObject([]); + }); + test('threats is valid when updated with all sub-objects', () => { + const expected: ThreatParams[] = [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ]; + 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', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).value.threats + ).toMatchObject(expected); + }); + test('threats is invalid when updated with missing property framework', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing tactic sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing techniques', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts new file mode 100644 index 0000000000000..244d8d1f5cc77 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts @@ -0,0 +1,63 @@ +/* + * 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'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { + enabled, + description, + false_positives, + filters, + from, + immutable, + index, + rule_id, + interval, + query, + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, + id, +} from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +export const updateRulesSchema = Joi.object({ + description, + enabled, + false_positives, + filters, + from, + rule_id, + id, + immutable, + index, + interval, + query: query.allow(''), + language, + output_index, + saved_id, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, +}).xor('id', 'rule_id'); 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 99af43ce51a12..b342cc5cd14ef 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 @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; -import { SignalsRequest } from '../../alerts/types'; -import { setSignalsStatusSchema } from '../schemas'; +import { SignalsRequest } from '../../signals/types'; +import { setSignalsStatusSchema } from '../schemas/set_signal_status_schema'; import { ServerFacade } from '../../../../types'; import { transformError, getIndex } from '../utils'; 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 4663ea357f259..8ca5c24d88100 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -6,495 +6,9 @@ import Boom from 'boom'; -import { - transformAlertToRule, - getIdError, - transformFindAlertsOrError, - transformOrError, - transformError, -} from './utils'; -import { getResult } from './__mocks__/request_responses'; +import { transformError } from './utils'; describe('utils', () => { - describe('transformAlertToRule', () => { - test('should work with a full data set', () => { - const fullRule = getResult(); - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should work with a partial data set missing data', () => { - const fullRule = getResult(); - const { from, language, ...omitData } = transformAlertToRule(fullRule); - expect(omitData).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit query if query is null', () => { - const fullRule = getResult(); - fullRule.params.query = null; - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit query if query is undefined', () => { - const fullRule = getResult(); - fullRule.params.query = undefined; - const rule = transformAlertToRule(fullRule); - expect(rule).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - output_index: '.siem-signals', - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should omit a mix of undefined, null, and missing fields', () => { - const fullRule = getResult(); - fullRule.params.query = undefined; - fullRule.params.language = null; - const { from, enabled, ...omitData } = transformAlertToRule(fullRule); - expect(omitData).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - max_signals: 100, - name: 'Detect Root/Admin Users', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should return enabled is equal to false', () => { - const fullRule = getResult(); - fullRule.enabled = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); - expect(ruleWithEnabledFalse).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: false, - from: 'now-6m', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - - test('should return immutable is equal to false', () => { - const fullRule = getResult(); - fullRule.params.immutable = false; - const ruleWithEnabledFalse = transformAlertToRule(fullRule); - expect(ruleWithEnabledFalse).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - from: 'now-6m', - false_positives: [], - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - language: 'kuery', - risk_score: 50, - rule_id: 'rule-1', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - 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/', - }, - ], - }, - ], - to: 'now', - type: 'query', - }); - }); - }); - - 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: "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: "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: "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: "rule-id-123" not found'); - }); - - test('outputs message about both being not defined when both are undefined', () => { - const boom = getIdError({ id: undefined, ruleId: undefined }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when both are null', () => { - const boom = getIdError({ id: null, ruleId: null }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when id is null and ruleId is undefined', () => { - const boom = getIdError({ id: null, ruleId: undefined }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - - test('outputs message about both being not defined when id is undefined and ruleId is null', () => { - const boom = getIdError({ id: undefined, ruleId: null }); - expect(boom.message).toEqual('id or rule_id should have been defined'); - }); - }); - - describe('transformFindAlertsOrError', () => { - test('outputs empty data set when data set is empty correct', () => { - const output = transformFindAlertsOrError({ data: [] }); - expect(output).toEqual({ data: [] }); - }); - - test('outputs 200 if the data is of type siem alert', () => { - const output = transformFindAlertsOrError({ - data: [getResult()], - }); - expect(output).toEqual({ - data: [ - { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - to: 'now', - 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/', - }, - ], - }, - ], - }, - ], - }); - }); - - test('returns 500 if the data is not of type siem alert', () => { - const output = transformFindAlertsOrError({ data: [{ random: 1 }] }); - expect((output as Boom).message).toEqual('Internal error transforming'); - }); - }); - - describe('transformOrError', () => { - test('outputs 200 if the data is of type siem alert', () => { - const output = transformOrError(getResult()); - expect(output).toEqual({ - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - output_index: '.siem-signals', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - rule_id: 'rule-1', - risk_score: 50, - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: [], - to: 'now', - 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/', - }, - ], - }, - ], - }); - }); - - test('returns 500 if the data is not of type siem alert', () => { - const output = transformOrError({ data: [{ random: 1 }] }); - expect((output as Boom).message).toEqual('Internal error transforming'); - }); - }); - describe('transformError', () => { test('returns boom if it is a boom object', () => { const boom = new Boom(''); 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 6df4174e628b3..aed0ced5cdeb5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -5,77 +5,9 @@ */ import Boom from 'boom'; -import { pickBy } from 'lodash/fp'; import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../common/constants'; -import { RuleAlertType, isAlertType, OutputRuleAlertRest, isAlertTypes } from '../alerts/types'; import { ServerFacade, RequestFacade } from '../../../types'; -export const getIdError = ({ - id, - ruleId, -}: { - id: string | undefined | null; - ruleId: string | undefined | null; -}) => { - if (id != null) { - return new Boom(`id: "${id}" not found`, { statusCode: 404 }); - } else if (ruleId != null) { - return new Boom(`rule_id: "${ruleId}" not found`, { statusCode: 404 }); - } else { - return new Boom(`id or rule_id should have been defined`, { statusCode: 404 }); - } -}; - -// Transforms the data but will remove any null or undefined it encounters and not include -// those on the export -export const transformAlertToRule = (alert: RuleAlertType): Partial => { - return pickBy((value: unknown) => value != null, { - created_by: alert.createdBy, - description: alert.params.description, - enabled: alert.enabled, - false_positives: alert.params.falsePositives, - filters: alert.params.filters, - from: alert.params.from, - id: alert.id, - immutable: alert.params.immutable, - index: alert.params.index, - interval: alert.interval, - rule_id: alert.params.ruleId, - language: alert.params.language, - output_index: alert.params.outputIndex, - max_signals: alert.params.maxSignals, - risk_score: alert.params.riskScore, - name: alert.name, - query: alert.params.query, - references: alert.params.references, - saved_id: alert.params.savedId, - meta: alert.params.meta, - severity: alert.params.severity, - updated_by: alert.updatedBy, - tags: alert.tags, - to: alert.params.to, - type: alert.params.type, - threats: alert.params.threats, - }); -}; - -export const transformFindAlertsOrError = (findResults: { data: unknown[] }): unknown | Boom => { - if (isAlertTypes(findResults.data)) { - findResults.data = findResults.data.map(alert => transformAlertToRule(alert)); - return findResults; - } else { - return new Boom('Internal error transforming', { statusCode: 500 }); - } -}; - -export const transformOrError = (alert: unknown): Partial | Boom => { - if (isAlertType(alert)) { - return transformAlertToRule(alert); - } else { - return new Boom('Internal error transforming', { statusCode: 500 }); - } -}; - export const transformError = (err: Error & { statusCode?: number }) => { if (Boom.isBoom(err)) { return err; 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/rules/create_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/delete_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/delete_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts new file mode 100644 index 0000000000000..5c0fa76b52620 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash/fp'; + +import { SIGNALS_ID } from '../../../../common/constants'; +import { AlertsClient } from '../../../../../alerting/server/alerts_client'; +import { ActionsClient } from '../../../../../actions/server/actions_client'; +import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; +import { RequestFacade } from '../../../types'; +import { Alert } from '../../../../../alerting/server/types'; + +export type UpdateRuleAlertParamsRest = Partial & { + id: string | undefined; + rule_id: RuleAlertParams['ruleId'] | undefined; +}; + +export interface FindParamsRest { + per_page: number; + page: number; + sort_field: string; + sort_order: 'asc' | 'desc'; + fields: string[]; + filter: string; +} + +export interface UpdateRulesRequest extends RequestFacade { + payload: UpdateRuleAlertParamsRest; +} + +export type RuleAlertType = Alert & { + id: string; + params: RuleTypeParams; +}; + +export interface RulesRequest extends RequestFacade { + payload: RuleAlertParamsRest; +} + +export interface FindRuleParams { + alertsClient: AlertsClient; + perPage?: number; + page?: number; + sortField?: string; + filter?: string; + fields?: string[]; + sortOrder?: 'asc' | 'desc'; +} + +export interface FindRulesRequest extends Omit { + query: { + per_page: number; + page: number; + search?: string; + sort_field?: string; + filter?: string; + fields?: string[]; + sort_order?: 'asc' | 'desc'; + }; +} + +export interface Clients { + alertsClient: AlertsClient; + actionsClient: ActionsClient; +} + +export type UpdateRuleParams = Partial & { + id: string | undefined | null; +} & Clients; + +export type DeleteRuleParams = Clients & { + id: string | undefined; + ruleId: string | undefined | null; +}; + +export type RuleParams = RuleAlertParams & Clients; + +export interface ReadRuleParams { + alertsClient: AlertsClient; + id?: string | undefined | null; + ruleId?: string | undefined | null; +} + +export interface ReadRuleByRuleId { + alertsClient: AlertsClient; + ruleId: string; +} + +export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { + return obj.every(rule => isAlertType(rule)); +}; + +export const isAlertType = (obj: unknown): obj is RuleAlertType => { + return get('alertTypeId', obj) === SIGNALS_ID; +}; + +export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { + return objArray.length === 0 || isAlertType(objArray[0]); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts 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/rules/update_rules.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts 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/signals/__mocks__/es_results.ts similarity index 94% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 4c113544e6e21..215d9da6eb7ff 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/signals/__mocks__/es_results.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SignalSourceHit, - SignalSearchResponse, - RuleTypeParams, - OutputRuleAlertRest, -} from '../types'; +import { SignalSourceHit, SignalSearchResponse } from '../types'; +import { Logger } from 'kibana/server'; +import { RuleTypeParams, OutputRuleAlertRest } from '../../types'; export const sampleRuleAlertParams = ( maxSignals?: number | undefined, @@ -281,3 +278,13 @@ export const sampleRule = (): Partial => { type: 'query', }; }; + +export const mockLogger: Logger = { + log: jest.fn(), + trace: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts new file mode 100644 index 0000000000000..e10158a0b879e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -0,0 +1,284 @@ +/* + * 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 { + sampleRuleAlertParams, + sampleDocNoSortId, + sampleRuleGuid, + sampleIdGuid, +} from './__mocks__/es_results'; +import { buildBulkBody } from './build_bulk_body'; + +describe('buildBulkBody', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if bulk body builds well-defined body', () => { + const sampleParams = sampleRuleAlertParams(); + const fakeSignalSourceHit = buildBulkBody({ + doc: sampleDocNoSortId(), + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + 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']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + 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']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + 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']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + module: 'system', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + 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']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_event: { + kind: 'event', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts new file mode 100644 index 0000000000000..6d9f442515b2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.ts @@ -0,0 +1,56 @@ +/* + * 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 { SignalSourceHit, SignalHit } from './types'; +import { buildRule } from './build_rule'; +import { buildSignal } from './build_signal'; +import { buildEventTypeSignal } from './build_event_type_signal'; +import { RuleTypeParams } from '../types'; + +interface BuildBulkBodyParams { + doc: SignalSourceHit; + ruleParams: RuleTypeParams; + id: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + tags: string[]; +} + +// format search_after result for signals index. +export const buildBulkBody = ({ + doc, + ruleParams, + id, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, +}: BuildBulkBodyParams): SignalHit => { + const rule = buildRule({ + ruleParams, + id, + name, + enabled, + createdBy, + updatedBy, + interval, + tags, + }); + const signal = buildSignal(doc, rule); + const event = buildEventTypeSignal(doc); + const signalHit: SignalHit = { + ...doc._source, + '@timestamp': new Date().toISOString(), + event, + signal, + }; + return signalHit; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.ts new file mode 100644 index 0000000000000..106a049002e05 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.test.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 { sampleDocNoSortId } from './__mocks__/es_results'; +import { buildEventTypeSignal } from './build_event_type_signal'; + +describe('buildEventTypeSignal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it returns the event appended of kind signal if it does not exist', () => { + const doc = sampleDocNoSortId(); + delete doc._source.event; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event appended of kind signal if it is an empty object', () => { + const doc = sampleDocNoSortId(); + doc._source.event = {}; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event with kind signal and other properties if they exist', () => { + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const eventType = buildEventTypeSignal(doc); + const expected: object = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'signal', + }; + expect(eventType).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts new file mode 100644 index 0000000000000..59cdc020c611d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_event_type_signal.ts @@ -0,0 +1,15 @@ +/* + * 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 { SignalSourceHit } from './types'; + +export const buildEventTypeSignal = (doc: SignalSourceHit): object => { + if (doc._source.event != null && doc._source.event instanceof Object) { + return { ...doc._source.event, kind: 'signal' }; + } else { + return { kind: 'signal' }; + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_events_query.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts new file mode 100644 index 0000000000000..c12c6fd333f56 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts @@ -0,0 +1,156 @@ +/* + * 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 { buildRule } from './build_rule'; +import { sampleRuleAlertParams, sampleRuleGuid } from './__mocks__/es_results'; +import { OutputRuleAlertRest } from '../types'; + +describe('buildRule', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it builds a rule as expected with filters present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ]; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: false, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + filters: [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ], + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if enabled is null if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if filters is undefined if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + tags: ['some fake tag 1', 'some fake tag 2'], + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts new file mode 100644 index 0000000000000..64ec989208b6a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts @@ -0,0 +1,59 @@ +/* + * 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 { pickBy } from 'lodash/fp'; +import { RuleTypeParams, OutputRuleAlertRest } from '../types'; + +interface BuildRuleParams { + ruleParams: RuleTypeParams; + name: string; + id: string; + enabled: boolean; + createdBy: string; + updatedBy: string; + interval: string; + tags: string[]; +} + +export const buildRule = ({ + ruleParams, + name, + id, + enabled, + createdBy, + updatedBy, + interval, + tags, +}: BuildRuleParams): Partial => { + return pickBy((value: unknown) => value != null, { + id, + rule_id: ruleParams.ruleId, + false_positives: ruleParams.falsePositives, + saved_id: ruleParams.savedId, + meta: ruleParams.meta, + max_signals: ruleParams.maxSignals, + risk_score: ruleParams.riskScore, + output_index: ruleParams.outputIndex, + description: ruleParams.description, + from: ruleParams.from, + immutable: ruleParams.immutable, + index: ruleParams.index, + interval, + language: ruleParams.language, + name, + query: ruleParams.query, + references: ruleParams.references, + severity: ruleParams.severity, + tags, + type: ruleParams.type, + to: ruleParams.to, + enabled, + filters: ruleParams.filters, + created_by: createdBy, + updated_by: updatedBy, + threats: ruleParams.threats, + }); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts new file mode 100644 index 0000000000000..1c024d0496743 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { sampleDocNoSortId, sampleRule } from './__mocks__/es_results'; +import { buildSignal } from './build_signal'; +import { OutputRuleAlertRest } from '../types'; +import { Signal } from './types'; + +describe('buildSignal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('it builds a signal as expected without original_event if event does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + delete doc._source.event; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a signal as expected with original_event if is present', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts new file mode 100644 index 0000000000000..4131c843297ea --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.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 { SignalSourceHit, Signal } from './types'; +import { OutputRuleAlertRest } from '../types'; + +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { + const signal: Signal = { + parent: { + id: doc._id, + type: 'event', + index: doc._index, + depth: 1, + }, + original_time: doc._source['@timestamp'], + status: 'open', + rule, + }; + if (doc._source.event != null) { + return { ...signal, original_event: doc._source.event }; + } + return signal; +}; 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/signals/get_filter.test.ts similarity index 99% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.test.ts index e1d10e2efdefb..43b5ce4b590a3 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/signals/get_filter.test.ts @@ -7,7 +7,7 @@ import { getQueryFilter, getFilter } from './get_filter'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { AlertServices } from '../../../../../alerting/server/types'; -import { PartialFilter } from './types'; +import { PartialFilter } from '../types'; describe('get_filter', () => { let savedObjectsClient = savedObjectsClientMock.create(); 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/signals/get_filter.ts similarity index 98% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts index 858f3580f57e8..8a67d0cb5c5b6 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/signals/get_filter.ts @@ -5,7 +5,6 @@ */ import { AlertServices } from '../../../../../alerting/server/types'; -import { RuleAlertParams, PartialFilter } from './types'; import { assertUnreachable } from '../../../utils/build_query'; import { Query, @@ -13,6 +12,7 @@ import { esFilters, IIndexPattern, } from '../../../../../../../../src/plugins/data/server'; +import { PartialFilter, RuleAlertParams } from '../types'; export const getQueryFilter = ( query: string, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts new file mode 100644 index 0000000000000..ac6f840943f18 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -0,0 +1,286 @@ +/* + * 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 { + sampleRuleAlertParams, + sampleEmptyDocSearchResults, + sampleRuleGuid, + mockLogger, + repeatedSearchResultsWithSortId, + sampleBulkCreateDuplicateResult, + sampleDocSearchResultsNoSortId, + sampleDocSearchResultsNoSortIdNoHits, +} from './__mocks__/es_results'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import uuid from 'uuid'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('searchAfterAndBulkCreate', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if successful with empty search results', async () => { + const sampleParams = sampleRuleAlertParams(); + const result = await searchAfterAndBulkCreate({ + someResult: sampleEmptyDocSearchResults, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockService.callCluster).toHaveBeenCalledTimes(0); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs', async () => { + const sampleParams = sampleRuleAlertParams(30); + const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockService.callCluster).toHaveBeenCalledTimes(5); + expect(result).toEqual(true); + }); + test('if unsuccessful first bulk create', async () => { + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + const sampleParams = sampleRuleAlertParams(10); + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(result).toEqual(false); + }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: sampleDocSearchResultsNoSortId(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(result).toEqual(false); + }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const result = await searchAfterAndBulkCreate({ + someResult: sampleDocSearchResultsNoSortIdNoHits(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockReturnValueOnce(sampleEmptyDocSearchResults); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(true); + }); + test('if returns false when singleSearchAfter throws an exception', async () => { + const sampleParams = sampleRuleAlertParams(10); + const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); + mockService.callCluster + .mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }) + .mockImplementation(() => { + throw Error('Fake Error'); + }); + const result = await searchAfterAndBulkCreate({ + someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + pageSize: 1, + filter: undefined, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(result).toEqual(false); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts new file mode 100644 index 0000000000000..fb314e62ba943 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -0,0 +1,135 @@ +/* + * 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 { RuleTypeParams } from '../types'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { Logger } from '../../../../../../../../src/core/server'; +import { singleSearchAfter } from './single_search_after'; +import { singleBulkCreate } from './single_bulk_create'; +import { SignalSearchResponse } from './types'; + +interface SearchAfterAndBulkCreateParams { + someResult: SignalSearchResponse; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + id: string; + signalsIndex: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + pageSize: number; + filter: unknown; + tags: string[]; +} + +// search_after through documents and re-index using bulk endpoint. +export const searchAfterAndBulkCreate = async ({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + filter, + name, + createdBy, + updatedBy, + interval, + enabled, + pageSize, + tags, +}: SearchAfterAndBulkCreateParams): Promise => { + if (someResult.hits.hits.length === 0) { + return true; + } + + logger.debug('[+] starting bulk insertion'); + await singleBulkCreate({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, + }); + const totalHits = + typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; + // maxTotalHitsSize represents the total number of docs to + // query for, no matter the size of each individual page of search results. + // If the total number of hits for the overall search result is greater than + // maxSignals, default to requesting a total of maxSignals, otherwise use the + // totalHits in the response from the searchAfter query. + const maxTotalHitsSize = totalHits >= ruleParams.maxSignals ? ruleParams.maxSignals : totalHits; + + // number of docs in the current search result + let hitsSize = someResult.hits.hits.length; + logger.debug(`first size: ${hitsSize}`); + let sortIds = someResult.hits.hits[0].sort; + if (sortIds == null && totalHits > 0) { + logger.error('sortIds was empty on first search but expected more'); + return false; + } else if (sortIds == null && totalHits === 0) { + return true; + } + let sortId; + if (sortIds != null) { + sortId = sortIds[0]; + } + while (hitsSize < maxTotalHitsSize && hitsSize !== 0) { + try { + logger.debug(`sortIds: ${sortIds}`); + const searchAfterResult: SignalSearchResponse = await singleSearchAfter({ + searchAfterSortId: sortId, + ruleParams, + services, + logger, + filter, + pageSize, // maximum number of docs to receive per search result. + }); + if (searchAfterResult.hits.hits.length === 0) { + return true; + } + hitsSize += searchAfterResult.hits.hits.length; + logger.debug(`size adjusted: ${hitsSize}`); + sortIds = searchAfterResult.hits.hits[0].sort; + if (sortIds == null) { + logger.debug('sortIds was empty on search'); + return true; // no more search results + } + sortId = sortIds[0]; + logger.debug('next bulk index'); + await singleBulkCreate({ + someResult: searchAfterResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, + }); + logger.debug('finished next bulk index'); + } catch (exc) { + logger.error(`[-] search_after and bulk threw an error ${exc}`); + return false; + } + } + logger.debug(`[+] completed bulk index of ${maxTotalHitsSize}`); + return true; +}; 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/signals/signal_rule_alert_type.ts similarity index 96% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 9823d8b3b9bea..37467e405dd8e 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/signals/signal_rule_alert_type.ts @@ -13,18 +13,18 @@ import { } from '../../../../common/constants'; import { buildEventsSearchQuery } from './build_events_query'; -import { searchAfterAndBulkCreate } from './utils'; -import { RuleAlertTypeDefinition } from './types'; -import { getFilter } from './get_filter'; import { getInputIndex } from './get_input_output_index'; +import { searchAfterAndBulkCreate } from './search_after_bulk_create'; +import { getFilter } from './get_filter'; +import { SignalRuleAlertTypeDefinition } from './types'; -export const rulesAlertType = ({ +export const signalRulesAlertType = ({ logger, version, }: { logger: Logger; version: string; -}): RuleAlertTypeDefinition => { +}): SignalRuleAlertTypeDefinition => { return { id: SIGNALS_ID, name: 'SIEM Signals', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts new file mode 100644 index 0000000000000..d58f0a22b763d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -0,0 +1,230 @@ +/* + * 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 { generateId } from './utils'; +import { + sampleRuleAlertParams, + sampleDocSearchResultsNoSortId, + mockLogger, + sampleRuleGuid, + sampleDocSearchResultsNoSortIdNoVersion, + sampleEmptyDocSearchResults, + sampleBulkCreateDuplicateResult, +} from './__mocks__/es_results'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; +import { singleBulkCreate } from './single_bulk_create'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('singleBulkCreate', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('create signal id gereateId', () => { + test('two docs with same index, id, and version should have same id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version, ruleId); + expect(firstHash).toEqual(generatedHash); + expect(secondHash).toEqual(generatedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + }); + test('two docs with different index, id, and version should have different id', () => { + const findex = 'myfakeindex'; + const findex2 = 'mysecondfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // 'mysecondfakeindexsomefakeid1rule-1' + const secondGeneratedHash = + 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex2, fid, version, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('two docs with same index, different id, and same version should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const fid2 = 'somefakeid2'; + const version = '1'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // 'myfakeindexsomefakeid21rule-1' + const secondGeneratedHash = + '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid2, version, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('two docs with same index, same id, and different version should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const version2 = '2'; + const ruleId = 'rule-1'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // myfakeindexsomefakeid2rule-1' + const secondGeneratedHash = + 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version2, ruleId); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + test('Ensure generated id is less than 512 bytes, even for really really long strings', () => { + const longIndexName = + 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + const firstHash = generateId(longIndexName, fid, version, ruleId); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + }); + test('two docs with same index, same id, same version number, and different rule ids should have different id', () => { + const findex = 'myfakeindex'; + const fid = 'somefakeid'; + const version = '1'; + const ruleId = 'rule-1'; + const ruleId2 = 'rule-2'; + // 'myfakeindexsomefakeid1rule-1' + const firstGeneratedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679'; + // myfakeindexsomefakeid1rule-2' + const secondGeneratedHash = + '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863'; + const firstHash = generateId(findex, fid, version, ruleId); + const secondHash = generateId(findex, fid, version, ruleId2); + expect(firstHash).toEqual(firstGeneratedHash); + expect(secondHash).toEqual(secondGeneratedHash); + expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field + expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512); + expect(firstHash).not.toEqual(secondHash); + }); + }); + test('create successful bulk create', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortId; + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create successful bulk create with docs with no versioning', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; + mockService.callCluster.mockReturnValueOnce({ + took: 100, + errors: false, + items: [ + { + fakeItemValue: 'fakeItemKey', + }, + ], + }); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create unsuccessful bulk create due to empty search results', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleEmptyDocSearchResults; + mockService.callCluster.mockReturnValue(false); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(successfulsingleBulkCreate).toEqual(true); + }); + test('create successful bulk create when bulk create has errors', async () => { + const sampleParams = sampleRuleAlertParams(); + const sampleSearchResult = sampleDocSearchResultsNoSortId; + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const successfulsingleBulkCreate = await singleBulkCreate({ + someResult: sampleSearchResult(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + tags: ['some fake tag 1', 'some fake tag 2'], + }); + expect(mockLogger.error).toHaveBeenCalled(); + expect(successfulsingleBulkCreate).toEqual(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts new file mode 100644 index 0000000000000..40b2eeab938dc --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -0,0 +1,106 @@ +/* + * 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 { performance } from 'perf_hooks'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { SignalSearchResponse, BulkResponse } from './types'; +import { RuleTypeParams } from '../types'; +import { generateId } from './utils'; +import { buildBulkBody } from './build_bulk_body'; +import { Logger } from '../../../../../../../../src/core/server'; + +interface SingleBulkCreateParams { + someResult: SignalSearchResponse; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + id: string; + signalsIndex: string; + name: string; + createdBy: string; + updatedBy: string; + interval: string; + enabled: boolean; + tags: string[]; +} + +// Bulk Index documents. +export const singleBulkCreate = async ({ + someResult, + ruleParams, + services, + logger, + id, + signalsIndex, + name, + createdBy, + updatedBy, + interval, + enabled, + tags, +}: SingleBulkCreateParams): Promise => { + if (someResult.hits.hits.length === 0) { + return true; + } + // index documents after creating an ID based on the + // source documents' originating index, and the original + // document _id. This will allow two documents from two + // different indexes with the same ID to be + // indexed, and prevents us from creating any updates + // to the documents once inserted into the signals index, + // while preventing duplicates from being added to the + // signals index if rules are re-run over the same time + // span. Also allow for versioning. + const bulkBody = someResult.hits.hits.flatMap(doc => [ + { + create: { + _index: signalsIndex, + _id: generateId( + doc._index, + doc._id, + doc._version ? doc._version.toString() : '', + ruleParams.ruleId ?? '' + ), + }, + }, + buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }), + ]); + const time1 = performance.now(); + const firstResult: BulkResponse = await services.callCluster('bulk', { + index: signalsIndex, + refresh: false, + body: bulkBody, + }); + const time2 = performance.now(); + 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 + // types of errors they are, count them up, and log them. + const errorCountMap = firstResult.items.reduce((acc: { [key: string]: number }, item) => { + if (item.create.error) { + const responseStatusKey = item.create.status.toString(); + acc[responseStatusKey] = acc[responseStatusKey] ? acc[responseStatusKey] + 1 : 1; + } + return acc; + }, {}); + /* + the logging output below should look like + {'409': 55} + which is read as "there were 55 counts of 409 errors returned from bulk create" + */ + logger.error( + `[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify( + errorCountMap, + null, + 2 + )}` + ); + } + return true; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.ts new file mode 100644 index 0000000000000..a5d1f66d3089e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.test.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 { savedObjectsClientMock } from 'src/core/server/mocks'; +import { + sampleRuleAlertParams, + sampleDocSearchResultsNoSortId, + mockLogger, + sampleDocSearchResultsWithSortId, +} from './__mocks__/es_results'; +import { singleSearchAfter } from './single_search_after'; + +export const mockService = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), +}; + +describe('singleSearchAfter', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('if singleSearchAfter works without a given sort id', async () => { + let searchAfterSortId; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); + await expect( + singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }) + ).rejects.toThrow('Attempted to search after with empty sort id'); + }); + test('if singleSearchAfter works with a given sort id', async () => { + const searchAfterSortId = '1234567891111'; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); + const searchAfterResult = await singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }); + expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId); + }); + test('if singleSearchAfter throws error', async () => { + const searchAfterSortId = '1234567891111'; + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockImplementation(async () => { + throw Error('Fake Error'); + }); + await expect( + singleSearchAfter({ + searchAfterSortId, + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + pageSize: 1, + filter: undefined, + }) + ).rejects.toThrow('Fake Error'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts new file mode 100644 index 0000000000000..3a99500cb3433 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_search_after.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RuleTypeParams } from '../types'; +import { AlertServices } from '../../../../../alerting/server/types'; +import { Logger } from '../../../../../../../../src/core/server'; +import { SignalSearchResponse } from './types'; +import { buildEventsSearchQuery } from './build_events_query'; + +interface SingleSearchAfterParams { + searchAfterSortId: string | undefined; + ruleParams: RuleTypeParams; + services: AlertServices; + logger: Logger; + pageSize: number; + filter: unknown; +} + +// utilize search_after for paging results into bulk. +export const singleSearchAfter = async ({ + searchAfterSortId, + ruleParams, + services, + filter, + logger, + pageSize, +}: SingleSearchAfterParams): Promise => { + if (searchAfterSortId == null) { + throw Error('Attempted to search after with empty sort id'); + } + try { + const searchAfterQuery = buildEventsSearchQuery({ + index: ruleParams.index, + from: ruleParams.from, + to: ruleParams.to, + filter, + size: pageSize, + searchAfterSortId, + }); + const nextSearchAfterResult: SignalSearchResponse = await services.callCluster( + 'search', + searchAfterQuery + ); + return nextSearchAfterResult; + } catch (exc) { + logger.error(`[-] nextSearchAfter threw an error ${exc}`); + throw exc; + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts new file mode 100644 index 0000000000000..213ceb29a6e25 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -0,0 +1,123 @@ +/* + * 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 { RuleAlertParams, OutputRuleAlertRest } from '../types'; +import { SearchResponse } from '../../types'; +import { RequestFacade } from '../../../types'; +import { AlertType, State, AlertExecutorOptions } from '../../../../../alerting/server/types'; + +export interface SignalsParams { + signalIds: string[] | undefined | null; + query: object | undefined | null; + status: 'open' | 'closed'; +} + +export type SignalsRestParams = Omit & { + signal_ids: SignalsParams['signalIds']; +}; + +export interface SignalsRequest extends RequestFacade { + payload: SignalsRestParams; +} + +export type SearchTypes = + | string + | string[] + | number + | number[] + | boolean + | boolean[] + | object + | object[]; + +export interface SignalSource { + [key: string]: SearchTypes; + '@timestamp': string; +} + +export interface BulkResponse { + took: number; + errors: boolean; + items: [ + { + create: { + _index: string; + _type?: string; + _id: string; + _version: number; + result?: string; + _shards?: { + total: number; + successful: number; + failed: number; + }; + _seq_no?: number; + _primary_term?: number; + status: number; + error?: { + type: string; + reason: string; + index_uuid?: string; + shard: string; + index: string; + }; + }; + } + ]; +} + +export interface MGetResponse { + docs: GetResponse[]; +} +export interface GetResponse { + _index: string; + _type: string; + _id: string; + _version: number; + _seq_no: number; + _primary_term: number; + found: boolean; + _source: SearchTypes; +} + +export type SignalSearchResponse = SearchResponse; +export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; + +export type RuleExecutorOptions = Omit & { + params: RuleAlertParams & { + scrollSize: number; + scrollLock: string; + }; +}; + +// This returns true because by default a RuleAlertTypeDefinition is an AlertType +// since we are only increasing the strictness of params. +export const isAlertExecutor = (obj: SignalRuleAlertTypeDefinition): obj is AlertType => { + return true; +}; + +export type SignalRuleAlertTypeDefinition = Omit & { + executor: ({ services, params, state }: RuleExecutorOptions) => Promise; +}; + +export interface Signal { + rule: Partial; + parent: { + id: string; + type: string; + index: string; + depth: number; + }; + original_time: string; + original_event?: SearchTypes; + status: 'open' | 'closed'; +} + +export interface SignalHit { + '@timestamp': string; + event: object; + signal: Partial; +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts new file mode 100644 index 0000000000000..f25ce1d905466 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts @@ -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 { createHash } from 'crypto'; + +export const generateId = ( + docIndex: string, + docId: string, + version: string, + ruleId: string +): string => + createHash('sha256') + .update(docIndex.concat(docId, version, ruleId)) + .digest('hex'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts new file mode 100644 index 0000000000000..d02595c368aa7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -0,0 +1,67 @@ +/* + * 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 { esFilters } from '../../../../../../../src/plugins/data/server'; + +export type PartialFilter = Partial; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} + +export interface ThreatParams { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} + +export interface RuleAlertParams { + description: string; + enabled: boolean; + falsePositives: string[]; + filters: PartialFilter[] | undefined | null; + from: string; + immutable: boolean; + index: string[]; + interval: string; + ruleId: string | undefined | null; + language: string | undefined | null; + maxSignals: number; + riskScore: number; + outputIndex: string; + name: string; + query: string | undefined | null; + references: string[]; + savedId: string | undefined | null; + meta: Record | undefined | null; + severity: string; + tags: string[]; + to: string; + threats: ThreatParams[] | undefined | null; + type: 'query' | 'saved_query'; +} + +export type RuleTypeParams = Omit; + +export type RuleAlertParamsRest = Omit< + RuleAlertParams, + 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' +> & { + rule_id: RuleAlertParams['ruleId']; + false_positives: RuleAlertParams['falsePositives']; + saved_id: RuleAlertParams['savedId']; + max_signals: RuleAlertParams['maxSignals']; + risk_score: RuleAlertParams['riskScore']; + output_index: RuleAlertParams['outputIndex']; +}; + +export type OutputRuleAlertRest = RuleAlertParamsRest & { + id: string; + created_by: string | undefined | null; + updated_by: string | undefined | null; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index e97a07e276dcf..9e4e477aa78d2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -23,7 +23,6 @@ import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; import { Timeline } from './timeline/saved_object'; import { TLS } from './tls'; -import { SearchTypes, OutputRuleAlertRest } from './detection_engine/alerts/types'; export * from './hosts'; @@ -55,25 +54,6 @@ export interface SiemContext { req: FrameworkRequest; } -export interface Signal { - rule: Partial; - parent: { - id: string; - type: string; - index: string; - depth: number; - }; - original_time: string; - original_event?: SearchTypes; - status: 'open' | 'closed'; -} - -export interface SignalHit { - '@timestamp': string; - event: object; - signal: Partial; -} - export interface TotalValue { value: number; relation: string; From 8115e500ff9daf8172d02baca533a48787b9dcfa Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 10 Dec 2019 10:46:56 -0500 Subject: [PATCH 36/56] [SIEM] [DETECTION ENG] Add MITRE ATT&CK (#52398) * add mitre attack enterprise * Add Mitre Att&ck on the about rule * review * fix internatiolazition * bugs review * fix ux with add reference --- x-pack/legacy/plugins/siem/package.json | 1 + .../components/add_item_form/index.tsx | 29 +- .../components/add_item_form/translations.ts | 14 - .../components/description_step/index.tsx | 124 +- .../create_rule/components/mitre/index.tsx | 171 + .../components/mitre/translations.ts | 36 + .../components/query_bar/index.tsx | 59 +- .../step_about_rule/default_value.ts | 7 + .../components/step_about_rule/index.tsx | 11 + .../components/step_about_rule/schema.tsx | 44 +- .../step_about_rule/translations.ts | 7 + .../components/step_define_rule/index.tsx | 267 +- .../components/step_define_rule/schema.tsx | 21 - .../detection_engine/create_rule/helpers.ts | 15 +- .../detection_engine/create_rule/index.tsx | 15 +- .../create_rule/translations.ts | 4 + .../detection_engine/create_rule/types.ts | 16 +- .../mitre/mitre_tactics_techniques.ts | 4696 +++++++++++++++++ .../pages/detection_engine/mitre/types.ts | 21 + .../extract_tactics_techniques_mitre.js | 113 + 20 files changed, 5441 insertions(+), 230 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts create mode 100644 x-pack/legacy/plugins/siem/scripts/extract_tactics_techniques_mitre.js diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index d239961ee75d7..ef6431327b5ab 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -5,6 +5,7 @@ "private": true, "license": "Elastic-License", "scripts": { + "extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js & node ../../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix", "build-graphql-types": "node scripts/generate_types_from_graphql.js", "cypress:open": "../../../node_modules/.bin/cypress open", "cypress:run": "../../../node_modules/.bin/cypress run --spec ./cypress/integration/**/*.spec.ts --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge --reportDir ../../../../target/kibana-siem/cypress/results > ../../../../target/kibana-siem/cypress/results/output.json; ../../../../node_modules/.bin/marge ../../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../../target/kibana-siem/cypress/results; mkdir -p ../../../../target/junit && cp ../../../../target/kibana-siem/cypress/results/*.xml ../../../../target/junit/ && exit $status;" diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx index 04bca0cdbd61b..e972cd21b6be9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/index.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash/fp'; import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; -import * as I18n from './translations'; +import * as CreateRuleI18n from '../../translations'; interface AddItemProps { addText: string; @@ -34,18 +34,21 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...inputsRef.current.slice(0, index), ...inputsRef.current.slice(index + 1), ]; - if (inputsRef.current[index] != null) { - inputsRef.current[index].value = 're-render'; - } + inputsRef.current = inputsRef.current.map((ref, i) => { + if (i >= index && inputsRef.current[index] != null) { + ref.value = 're-render'; + } + return ref; + }); }, [field] ); const addItem = useCallback(() => { const values = field.value as string[]; - if (!isEmpty(values[values.length - 1])) { + if (!isEmpty(values) && values[values.length - 1]) { field.setValue([...values, '']); - } else { + } else if (isEmpty(values)) { field.setValue(['']); } }, [field]); @@ -62,9 +65,12 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...inputsRef.current.slice(index + 1), ]; setHaveBeenKeyboardDeleted(inputsRef.current.length - 1); - if (inputsRef.current[index] != null) { - inputsRef.current[index].value = 're-render'; - } + inputsRef.current = inputsRef.current.map((ref, i) => { + if (i >= index && inputsRef.current[index] != null) { + ref.value = 're-render'; + } + return ref; + }); } else { field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); } @@ -114,7 +120,8 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad ...(index === values.length - 1 ? { inputRef: handleLastInputRef.bind(null, index) } : {}), - ...(inputsRef.current[index] != null && inputsRef.current[index].value !== item + ...((inputsRef.current[index] != null && inputsRef.current[index].value !== item) || + inputsRef.current[index] == null ? { value: item } : {}), }; @@ -127,7 +134,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad iconType="trash" isDisabled={isDisabled} onClick={() => removeItem(index)} - aria-label={I18n.DELETE} + aria-label={CreateRuleI18n.DELETE} /> } onChange={e => updateItem(e, index)} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.ts deleted file mode 100644 index 98c15606d88fe..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/add_item_form/translations.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 { i18n } from '@kbn/i18n'; - -export const DELETE = i18n.translate( - 'xpack.siem.detectionEngine.createRule.addItem.deleteDescription', - { - defaultMessage: 'Delete', - } -); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx index 3e8147e5ca3c1..29e1bc228e066 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/description_step/index.tsx @@ -4,7 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui'; +import { + EuiBadge, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiTextArea, + EuiLink, + EuiText, + EuiListGroup, +} from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; import React, { memo, ReactNode } from 'react'; import styled from 'styled-components'; @@ -20,6 +29,9 @@ import { FilterLabel } from './filter_label'; import { FormSchema } from '../shared_imports'; import * as I18n from './translations'; +import { IMitreEnterpriseAttack } from '../../types'; +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; + interface StepRuleDescriptionProps { data: unknown; indexPatterns?: IIndexPattern; @@ -36,16 +48,34 @@ const EuiFlexItemWidth = styled(EuiFlexItem)` width: 50%; `; +const MyEuiListGroup = styled(EuiListGroup)` + padding: 0px; + .euiListGroupItem__button { + padding: 0px; + } +`; + +const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` + .euiFlexItem { + margin-bottom: 0px; + } +`; + export const StepRuleDescription = memo( ({ data, indexPatterns, schema }) => { const keys = Object.keys(schema); + const listItems = keys.reduce( + (acc: ListItems[], key: string) => [ + ...acc, + ...buildListItems(data, pick(key, schema), indexPatterns), + ], + [] + ); return ( - {chunk(keys.includes('queryBar') ? 3 : Math.ceil(keys.length / 2), keys).map(key => ( - - + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => ( + + ))} @@ -77,7 +107,9 @@ const getDescriptionItem = ( value: unknown, indexPatterns?: IIndexPattern ): ListItems[] => { - if (field === 'queryBar' && indexPatterns != null) { + if (field === 'useIndicesConfig') { + return []; + } else if (field === 'queryBar' && indexPatterns != null) { const filters = get('queryBar.filters', value) as esFilters.Filter[]; const query = get('queryBar.query', value) as Query; const savedId = get('queryBar.saved_id', value); @@ -123,6 +155,50 @@ const getDescriptionItem = ( ]; } return items; + } else if (field === 'threats') { + const threats: IMitreEnterpriseAttack[] = get(field, value).filter( + (threat: IMitreEnterpriseAttack) => threat.tactic.name !== 'none' + ); + if (threats.length > 0) { + return [ + { + title: label, + description: ( + + {threats.map((threat, index) => { + const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + return ( + + +
    + + {tactic != null ? tactic.text : ''} + +
    + { + const myTechnique = techniquesOptions.find( + t => t.name === technique.name + ); + return { + label: myTechnique != null ? myTechnique.label : '', + href: technique.reference, + target: '_blank', + }; + })} + /> +
    +
    + ); + })} +
    + ), + }, + ]; + } + return []; } else if (field === 'description') { return [ { @@ -131,20 +207,26 @@ const getDescriptionItem = ( }, ]; } else if (Array.isArray(get(field, value))) { - return [ - { - title: label, - description: ( - - {get(field, value).map((val: string) => ( - - {val} - - ))} - - ), - }, - ]; + const values: string[] = get(field, value); + if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + {val} + + ) + )} + + ), + }, + ]; + } + return []; } return [ { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx new file mode 100644 index 0000000000000..6ab4ca4b51447 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/index.tsx @@ -0,0 +1,171 @@ +/* + * 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 { + EuiButtonEmpty, + EuiButtonIcon, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiComboBox, + EuiFormControlLayout, +} from '@elastic/eui'; +import { isEmpty, kebabCase, camelCase } from 'lodash/fp'; +import React, { ChangeEvent, useCallback } from 'react'; +import styled from 'styled-components'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; +import * as CreateRuleI18n from '../../translations'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; +import * as I18n from './translations'; +import { IMitreEnterpriseAttack } from '../../types'; + +const MyEuiFormControlLayout = styled(EuiFormControlLayout)` + &.euiFormControlLayout--compressed { + height: fit-content !important; + } +`; +interface AddItemProps { + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; +} + +export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const removeItem = useCallback( + (index: number) => { + const values = field.value as string[]; + field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + }, + [field] + ); + + const addItem = useCallback(() => { + const values = field.value as IMitreEnterpriseAttack[]; + if (!isEmpty(values[values.length - 1])) { + field.setValue([ + ...values, + { tactic: { id: 'none', name: 'none', reference: 'none' }, techniques: [] }, + ]); + } else { + field.setValue([{ tactic: { id: 'none', name: 'none', reference: 'none' }, techniques: [] }]); + } + }, [field]); + + const updateTactic = useCallback( + (index: number, event: ChangeEvent) => { + const values = field.value as IMitreEnterpriseAttack[]; + const { id, reference, name } = tacticsOptions.find(t => t.value === event.target.value) || { + id: '', + name: '', + reference: '', + }; + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + tactic: { id, reference, name }, + techniques: [], + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const updateTechniques = useCallback( + (index: number, selectedOptions: unknown[]) => { + field.setValue([ + ...values.slice(0, index), + { + ...values[index], + techniques: selectedOptions, + }, + ...values.slice(index + 1), + ]); + }, + [field] + ); + + const values = field.value as IMitreEnterpriseAttack[]; + + return ( + + <> + {values.map((item, index) => { + const euiSelectFieldProps = { + disabled: isDisabled, + }; + return ( +
    + + + ({ text: t.text, value: t.value })), + ]} + aria-label="" + onChange={updateTactic.bind(null, index)} + prepend={I18n.TACTIC} + compressed + fullWidth={false} + value={camelCase(item.tactic.name)} + {...euiSelectFieldProps} + /> + + + + + t.tactics.includes(kebabCase(item.tactic.name)) + )} + selectedOptions={item.techniques} + onChange={updateTechniques.bind(null, index)} + isDisabled={isDisabled} + fullWidth={true} + /> + + + + removeItem(index)} + aria-label={CreateRuleI18n.DELETE} + /> + + + {values.length - 1 !== index && } +
    + ); + })} + + {I18n.ADD_MITRE_ATTACK} + + +
    + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts new file mode 100644 index 0000000000000..22ee6cc3ef911 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/mitre/translations.ts @@ -0,0 +1,36 @@ +/* + * 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 TACTIC = i18n.translate('xpack.siem.detectionEngine.mitreAttack.tacticsDescription', { + defaultMessage: 'Tactic', +}); + +export const TECHNIQUES = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.techniquesDescription', + { + defaultMessage: 'Techniques', + } +); + +export const ADD_MITRE_ATTACK = i18n.translate('xpack.siem.detectionEngine.mitreAttack.addTitle', { + defaultMessage: 'Add MITRE ATT&CK threat', +}); + +export const TECHNIQUES_PLACEHOLDER = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.techniquesPlaceHolderDescription', + { + defaultMessage: 'Select techniques ...', + } +); + +export const TACTIC_PLACEHOLDER = i18n.translate( + 'xpack.siem.detectionEngine.mitreAttack.tacticPlaceHolderDescription', + { + defaultMessage: 'Select tactic ...', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx index 92b2f557d4cec..8dc402f00e621 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/query_bar/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFormRow } from '@elastic/eui'; +import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback, useEffect, useState } from 'react'; import { Subscription } from 'rxjs'; @@ -36,6 +36,7 @@ interface QueryBarDefineRuleProps { idAria: string; isLoading: boolean; indexPattern: IIndexPattern; + resizeParentContainer?: (height: number) => void; } const StyledEuiFormRow = styled(EuiFormRow)` @@ -60,7 +61,9 @@ export const QueryBarDefineRule = ({ idAria, indexPattern, isLoading = false, + resizeParentContainer, }: QueryBarDefineRuleProps) => { + const [originalHeight, setOriginalHeight] = useState(-1); const [savedQuery, setSavedQuery] = useState(null); const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); @@ -165,6 +168,27 @@ export const QueryBarDefineRule = ({ [field.value] ); + const onMutation = (event: unknown, observer: unknown) => { + if (resizeParentContainer != null) { + const suggestionContainer = document.getElementById('kbnTypeahead__items'); + if (suggestionContainer != null) { + const box = suggestionContainer.getBoundingClientRect(); + const accordionContainer = document.getElementById('define-rule'); + if (accordionContainer != null) { + const accordionBox = accordionContainer.getBoundingClientRect(); + if (originalHeight === -1 || accordionBox.height < originalHeight + box.height) { + resizeParentContainer(originalHeight + box.height - 100); + } + if (originalHeight === -1) { + setOriginalHeight(accordionBox.height); + } + } + } else { + resizeParentContainer(-1); + } + } + }; + return ( - + + {mutationRef => ( +
    + +
    + )} +
    ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts index 7c4d78f364479..504b5ca85a3ab 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/default_value.ts @@ -15,4 +15,11 @@ export const defaultValue: AboutStepRule = { references: [''], falsePositives: [''], tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { id: 'none', name: 'none', reference: 'none' }, + techniques: [], + }, + ], }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx index 56830f252748f..aeb70061c44bf 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/index.tsx @@ -16,6 +16,7 @@ import { defaultValue } from './default_value'; import { schema } from './schema'; import * as I18n from './translations'; import { StepRuleDescription } from '../description_step'; +import { AddMitreThreat } from '../mitre'; const CommonUseField = getUseField({ component: Field }); @@ -114,6 +115,16 @@ export const StepAboutRule = memo(({ isEditView, isLoading, setSt dataTestSubj: 'detectionEngineStepAboutRuleFalsePositives', }} /> + {CreateRuleI18n.OPTIONAL_FIELD}, }, + threats: { + label: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', + { + defaultMessage: 'MITRE ATT&CK', + } + ), + labelAppend: {CreateRuleI18n.OPTIONAL_FIELD}, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as IMitreEnterpriseAttack[]).forEach(v => { + if (isEmpty(v.tactic.name) || (v.tactic.name !== 'none' && isEmpty(v.techniques))) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_MISSING', + path, + message: I18n.CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED, + } + : undefined; + }, + }, + ], + }, tags: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts index bd759b345d70d..017d4fe6fdf49 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_about_rule/translations.ts @@ -47,3 +47,10 @@ export const CRITICAL = i18n.translate( defaultMessage: 'Critical', } ); + +export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.customMitreAttackTechniquesFieldRequiredError', + { + defaultMessage: 'At least one Technique is required with a Tactic.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx index 26306d3573926..6954bd6bf733f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/index.tsx @@ -6,11 +6,11 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules/fetch_index_patterns'; -import { DEFAULT_INDEX_KEY, DEFAULT_SIGNALS_INDEX_KEY } from '../../../../../../common/constants'; +import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants'; import { useKibanaUiSetting } from '../../../../../lib/settings/use_kibana_ui_setting'; import * as CreateRuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; @@ -22,148 +22,133 @@ import * as I18n from './translations'; const CommonUseField = getUseField({ component: Field }); -export const StepDefineRule = memo(({ isEditView, isLoading, setStepData }) => { - const [initializeOutputIndex, setInitializeOutputIndex] = useState(true); - const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); - const [ - { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, - setIndices, - ] = useFetchIndexPatterns(); - const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - const [signalIndexConfig] = useKibanaUiSetting(DEFAULT_SIGNALS_INDEX_KEY); - const [myStepData, setMyStepData] = useState({ - index: indicesConfig || [], - isNew: true, - outputIndex: signalIndexConfig, - queryBar: { - query: { query: '', language: 'kuery' }, - filters: [], - saved_id: null, - }, - useIndicesConfig: 'true', - }); - const { form } = useForm({ - schema, - defaultValue: myStepData, - options: { stripEmptyFields: false }, - }); +export const StepDefineRule = memo( + ({ isEditView, isLoading, resizeParentContainer, setStepData }) => { + const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); + const [ + { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, + setIndices, + ] = useFetchIndexPatterns(); + const [indicesConfig] = useKibanaUiSetting(DEFAULT_INDEX_KEY); + const [myStepData, setMyStepData] = useState({ + index: indicesConfig || [], + isNew: true, + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: null, + }, + useIndicesConfig: 'true', + }); + const { form } = useForm({ + schema, + defaultValue: myStepData, + options: { stripEmptyFields: false }, + }); - const onSubmit = useCallback(async () => { - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.defineRule, data, isValid); - setMyStepData({ ...data, isNew: false } as DefineStepRule); - } - }, [form]); + const onSubmit = useCallback(async () => { + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); + } + }, [form]); - useEffect(() => { - if (signalIndexConfig != null && initializeOutputIndex) { - const outputIndexField = form.getFields().outputIndex; - outputIndexField.setValue(signalIndexConfig); - setInitializeOutputIndex(false); - } - }, [initializeOutputIndex, signalIndexConfig, form]); - - return isEditView && myStepData != null ? ( - - ) : ( - <> -
    - - + ) : ( + <> + + + + - - - - {({ useIndicesConfig }) => { - if (localUseIndicesConfig !== useIndicesConfig) { - const indexField = form.getFields().index; - if ( - indexField != null && - useIndicesConfig === 'true' && - !isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue(indicesConfig); - setIndices(indicesConfig); - } else if ( - indexField != null && - useIndicesConfig === 'false' && - isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue([]); - setIndices([]); + isLoading: indexPatternLoadingQueryBar, + dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + resizeParentContainer, + }} + /> + + {({ useIndicesConfig }) => { + if (localUseIndicesConfig !== useIndicesConfig) { + const indexField = form.getFields().index; + if ( + indexField != null && + useIndicesConfig === 'true' && + !isEqual(indexField.value, indicesConfig) + ) { + indexField.setValue(indicesConfig); + setIndices(indicesConfig); + } else if ( + indexField != null && + useIndicesConfig === 'false' && + isEqual(indexField.value, indicesConfig) + ) { + indexField.setValue([]); + setIndices([]); + } + setLocalUseIndicesConfig(useIndicesConfig); } - setLocalUseIndicesConfig(useIndicesConfig); - } - return null; - }} - - - - - - - {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} - - - - - ); -}); + return null; + }} + + + + + + + {myStepData.isNew ? CreateRuleI18n.CONTINUE : CreateRuleI18n.UPDATE} + + + + + ); + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx index 9f1644e73bf0b..0f6c5f72e1683 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx @@ -26,27 +26,6 @@ import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY } from './translations'; const { emptyField } = fieldValidators; export const schema: FormSchema = { - outputIndex: { - type: FIELD_TYPES.TEXT, - label: i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.fieldOutputIndiceNameLabel', - { - defaultMessage: 'Output index name', - } - ), - validations: [ - { - validator: emptyField( - i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', - { - defaultMessage: 'An output indice name for signals is required.', - } - ) - ), - }, - ], - }, useIndicesConfig: { type: FIELD_TYPES.RADIO_GROUP, label: i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts index b864260dd3338..f6546a680ad81 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/helpers.ts @@ -40,13 +40,12 @@ const getTimeTypeValue = (time: string): { unit: string; value: number } => { }; const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { - const { queryBar, useIndicesConfig, outputIndex, ...rest } = defineStepData; + const { queryBar, useIndicesConfig, ...rest } = defineStepData; const { filters, query, saved_id: savedId } = queryBar; return { ...rest, language: query.language, filters, - output_index: outputIndex, query: query.query as string, ...(savedId != null ? { saved_id: savedId } : {}), }; @@ -69,12 +68,22 @@ const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRul }; const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, ...rest } = aboutStepData; + const { falsePositives, references, riskScore, threats, ...rest } = aboutStepData; return { false_positives: falsePositives.filter(item => !isEmpty(item)), references: references.filter(item => !isEmpty(item)), risk_score: riskScore, + threats: threats + .filter(threat => threat.tactic.name !== 'none') + .map(threat => ({ + ...threat, + framework: 'MITRE ATT&CK', + techniques: threat.techniques.map(technique => { + const { id, name, reference } = technique; + return { id, name, reference }; + }), + })), ...rest, }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx index 878c7171d19ed..393b72d16b0a4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/index.tsx @@ -7,6 +7,7 @@ import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; +import styled from 'styled-components'; import { HeaderPage } from '../../../components/header_page'; import { WrapperPage } from '../../../components/wrapper_page'; @@ -24,7 +25,16 @@ import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; +const ResizeEuiPanel = styled(EuiPanel)<{ + height?: number; +}>` + .euiAccordion__childWrapper { + height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')}; + } +`; + export const CreateRuleComponent = React.memo(() => { + const [heightAccordion, setHeightAccordion] = useState(-1); const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); @@ -169,7 +179,7 @@ export const CreateRuleComponent = React.memo(() => { isLoading={isLoading} title={i18n.PAGE_TITLE} /> - + { isEditView={isStepRuleInEditView[RuleStep.defineRule]} isLoading={isLoading} setStepData={setStepData} + resizeParentContainer={height => setHeightAccordion(height)} /> - + void; isEditView: boolean; isLoading: boolean; + resizeParentContainer?: (height: number) => void; } interface StepRuleData { @@ -36,10 +37,10 @@ export interface AboutStepRule extends StepRuleData { references: string[]; falsePositives: string[]; tags: string[]; + threats: IMitreEnterpriseAttack[]; } export interface DefineStepRule extends StepRuleData { - outputIndex: string; useIndicesConfig: string; index: string[]; queryBar: FieldValueQueryBar; @@ -53,7 +54,6 @@ export interface ScheduleStepRule extends StepRuleData { } export interface DefineStepRuleJson { - output_index: string; index: string[]; filters: esFilters.Filter[]; saved_id?: string; @@ -69,8 +69,20 @@ export interface AboutStepRuleJson { references: string[]; false_positives: string[]; tags: string[]; + threats: IMitreEnterpriseAttack[]; } export type ScheduleStepRuleJson = ScheduleStepRule; export type FormatRuleType = 'query' | 'saved_query'; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} +export interface IMitreEnterpriseAttack { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts new file mode 100644 index 0000000000000..160e006c4d267 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/mitre_tactics_techniques.ts @@ -0,0 +1,4696 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { MitreTacticsOptions, MitreTechniquesOptions } from './types'; + +export const tactics = [ + { + name: 'Collection', + id: 'TA0009', + reference: 'https://attack.mitre.org/tactics/TA0009', + }, + { + name: 'Command and Control', + id: 'TA0011', + reference: 'https://attack.mitre.org/tactics/TA0011', + }, + { + name: 'Credential Access', + id: 'TA0006', + reference: 'https://attack.mitre.org/tactics/TA0006', + }, + { + name: 'Defense Evasion', + id: 'TA0005', + reference: 'https://attack.mitre.org/tactics/TA0005', + }, + { + name: 'Discovery', + id: 'TA0007', + reference: 'https://attack.mitre.org/tactics/TA0007', + }, + { + name: 'Execution', + id: 'TA0002', + reference: 'https://attack.mitre.org/tactics/TA0002', + }, + { + name: 'Exfiltration', + id: 'TA0010', + reference: 'https://attack.mitre.org/tactics/TA0010', + }, + { + name: 'Impact', + id: 'TA0040', + reference: 'https://attack.mitre.org/tactics/TA0040', + }, + { + name: 'Initial Access', + id: 'TA0001', + reference: 'https://attack.mitre.org/tactics/TA0001', + }, + { + name: 'Lateral Movement', + id: 'TA0008', + reference: 'https://attack.mitre.org/tactics/TA0008', + }, + { + name: 'Persistence', + id: 'TA0003', + reference: 'https://attack.mitre.org/tactics/TA0003', + }, + { + name: 'Privilege Escalation', + id: 'TA0004', + reference: 'https://attack.mitre.org/tactics/TA0004', + }, +]; + +export const tacticsOptions: MitreTacticsOptions[] = [ + { + id: 'TA0009', + name: 'Collection', + reference: 'https://attack.mitre.org/tactics/TA0009', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.collectionDescription', { + defaultMessage: 'Collection (TA0009)', + }), + value: 'collection', + }, + { + id: 'TA0011', + name: 'Command and Control', + reference: 'https://attack.mitre.org/tactics/TA0011', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.commandAndControlDescription', + { defaultMessage: 'Command and Control (TA0011)' } + ), + value: 'commandAndControl', + }, + { + id: 'TA0006', + name: 'Credential Access', + reference: 'https://attack.mitre.org/tactics/TA0006', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.credentialAccessDescription', + { defaultMessage: 'Credential Access (TA0006)' } + ), + value: 'credentialAccess', + }, + { + id: 'TA0005', + name: 'Defense Evasion', + reference: 'https://attack.mitre.org/tactics/TA0005', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.defenseEvasionDescription', + { defaultMessage: 'Defense Evasion (TA0005)' } + ), + value: 'defenseEvasion', + }, + { + id: 'TA0007', + name: 'Discovery', + reference: 'https://attack.mitre.org/tactics/TA0007', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.discoveryDescription', { + defaultMessage: 'Discovery (TA0007)', + }), + value: 'discovery', + }, + { + id: 'TA0002', + name: 'Execution', + reference: 'https://attack.mitre.org/tactics/TA0002', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.executionDescription', { + defaultMessage: 'Execution (TA0002)', + }), + value: 'execution', + }, + { + id: 'TA0010', + name: 'Exfiltration', + reference: 'https://attack.mitre.org/tactics/TA0010', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.exfiltrationDescription', { + defaultMessage: 'Exfiltration (TA0010)', + }), + value: 'exfiltration', + }, + { + id: 'TA0040', + name: 'Impact', + reference: 'https://attack.mitre.org/tactics/TA0040', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.impactDescription', { + defaultMessage: 'Impact (TA0040)', + }), + value: 'impact', + }, + { + id: 'TA0001', + name: 'Initial Access', + reference: 'https://attack.mitre.org/tactics/TA0001', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.initialAccessDescription', { + defaultMessage: 'Initial Access (TA0001)', + }), + value: 'initialAccess', + }, + { + id: 'TA0008', + name: 'Lateral Movement', + reference: 'https://attack.mitre.org/tactics/TA0008', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.lateralMovementDescription', + { defaultMessage: 'Lateral Movement (TA0008)' } + ), + value: 'lateralMovement', + }, + { + id: 'TA0003', + name: 'Persistence', + reference: 'https://attack.mitre.org/tactics/TA0003', + text: i18n.translate('xpack.siem.detectionEngine.mitreAttackTactics.persistenceDescription', { + defaultMessage: 'Persistence (TA0003)', + }), + value: 'persistence', + }, + { + id: 'TA0004', + name: 'Privilege Escalation', + reference: 'https://attack.mitre.org/tactics/TA0004', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.privilegeEscalationDescription', + { defaultMessage: 'Privilege Escalation (TA0004)' } + ), + value: 'privilegeEscalation', + }, +]; + +export const techniques = [ + { + name: '.bash_profile and .bashrc', + id: 'T1156', + reference: 'https://attack.mitre.org/techniques/T1156', + tactics: ['persistence'], + }, + { + name: 'Access Token Manipulation', + id: 'T1134', + reference: 'https://attack.mitre.org/techniques/T1134', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Accessibility Features', + id: 'T1015', + reference: 'https://attack.mitre.org/techniques/T1015', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Account Access Removal', + id: 'T1531', + reference: 'https://attack.mitre.org/techniques/T1531', + tactics: ['impact'], + }, + { + name: 'Account Discovery', + id: 'T1087', + reference: 'https://attack.mitre.org/techniques/T1087', + tactics: ['discovery'], + }, + { + name: 'Account Manipulation', + id: 'T1098', + reference: 'https://attack.mitre.org/techniques/T1098', + tactics: ['credential-access', 'persistence'], + }, + { + name: 'AppCert DLLs', + id: 'T1182', + reference: 'https://attack.mitre.org/techniques/T1182', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'AppInit DLLs', + id: 'T1103', + reference: 'https://attack.mitre.org/techniques/T1103', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'AppleScript', + id: 'T1155', + reference: 'https://attack.mitre.org/techniques/T1155', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Application Access Token', + id: 'T1527', + reference: 'https://attack.mitre.org/techniques/T1527', + tactics: ['defense-evasion', 'lateral-movement'], + }, + { + name: 'Application Deployment Software', + id: 'T1017', + reference: 'https://attack.mitre.org/techniques/T1017', + tactics: ['lateral-movement'], + }, + { + name: 'Application Shimming', + id: 'T1138', + reference: 'https://attack.mitre.org/techniques/T1138', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Application Window Discovery', + id: 'T1010', + reference: 'https://attack.mitre.org/techniques/T1010', + tactics: ['discovery'], + }, + { + name: 'Audio Capture', + id: 'T1123', + reference: 'https://attack.mitre.org/techniques/T1123', + tactics: ['collection'], + }, + { + name: 'Authentication Package', + id: 'T1131', + reference: 'https://attack.mitre.org/techniques/T1131', + tactics: ['persistence'], + }, + { + name: 'Automated Collection', + id: 'T1119', + reference: 'https://attack.mitre.org/techniques/T1119', + tactics: ['collection'], + }, + { + name: 'Automated Exfiltration', + id: 'T1020', + reference: 'https://attack.mitre.org/techniques/T1020', + tactics: ['exfiltration'], + }, + { + name: 'BITS Jobs', + id: 'T1197', + reference: 'https://attack.mitre.org/techniques/T1197', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Bash History', + id: 'T1139', + reference: 'https://attack.mitre.org/techniques/T1139', + tactics: ['credential-access'], + }, + { + name: 'Binary Padding', + id: 'T1009', + reference: 'https://attack.mitre.org/techniques/T1009', + tactics: ['defense-evasion'], + }, + { + name: 'Bootkit', + id: 'T1067', + reference: 'https://attack.mitre.org/techniques/T1067', + tactics: ['persistence'], + }, + { + name: 'Browser Bookmark Discovery', + id: 'T1217', + reference: 'https://attack.mitre.org/techniques/T1217', + tactics: ['discovery'], + }, + { + name: 'Browser Extensions', + id: 'T1176', + reference: 'https://attack.mitre.org/techniques/T1176', + tactics: ['persistence'], + }, + { + name: 'Brute Force', + id: 'T1110', + reference: 'https://attack.mitre.org/techniques/T1110', + tactics: ['credential-access'], + }, + { + name: 'Bypass User Account Control', + id: 'T1088', + reference: 'https://attack.mitre.org/techniques/T1088', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'CMSTP', + id: 'T1191', + reference: 'https://attack.mitre.org/techniques/T1191', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Change Default File Association', + id: 'T1042', + reference: 'https://attack.mitre.org/techniques/T1042', + tactics: ['persistence'], + }, + { + name: 'Clear Command History', + id: 'T1146', + reference: 'https://attack.mitre.org/techniques/T1146', + tactics: ['defense-evasion'], + }, + { + name: 'Clipboard Data', + id: 'T1115', + reference: 'https://attack.mitre.org/techniques/T1115', + tactics: ['collection'], + }, + { + name: 'Cloud Instance Metadata API', + id: 'T1522', + reference: 'https://attack.mitre.org/techniques/T1522', + tactics: ['credential-access'], + }, + { + name: 'Cloud Service Dashboard', + id: 'T1538', + reference: 'https://attack.mitre.org/techniques/T1538', + tactics: ['discovery'], + }, + { + name: 'Cloud Service Discovery', + id: 'T1526', + reference: 'https://attack.mitre.org/techniques/T1526', + tactics: ['discovery'], + }, + { + name: 'Code Signing', + id: 'T1116', + reference: 'https://attack.mitre.org/techniques/T1116', + tactics: ['defense-evasion'], + }, + { + name: 'Command-Line Interface', + id: 'T1059', + reference: 'https://attack.mitre.org/techniques/T1059', + tactics: ['execution'], + }, + { + name: 'Commonly Used Port', + id: 'T1043', + reference: 'https://attack.mitre.org/techniques/T1043', + tactics: ['command-and-control'], + }, + { + name: 'Communication Through Removable Media', + id: 'T1092', + reference: 'https://attack.mitre.org/techniques/T1092', + tactics: ['command-and-control'], + }, + { + name: 'Compile After Delivery', + id: 'T1500', + reference: 'https://attack.mitre.org/techniques/T1500', + tactics: ['defense-evasion'], + }, + { + name: 'Compiled HTML File', + id: 'T1223', + reference: 'https://attack.mitre.org/techniques/T1223', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Component Firmware', + id: 'T1109', + reference: 'https://attack.mitre.org/techniques/T1109', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Component Object Model Hijacking', + id: 'T1122', + reference: 'https://attack.mitre.org/techniques/T1122', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Component Object Model and Distributed COM', + id: 'T1175', + reference: 'https://attack.mitre.org/techniques/T1175', + tactics: ['lateral-movement', 'execution'], + }, + { + name: 'Connection Proxy', + id: 'T1090', + reference: 'https://attack.mitre.org/techniques/T1090', + tactics: ['command-and-control', 'defense-evasion'], + }, + { + name: 'Control Panel Items', + id: 'T1196', + reference: 'https://attack.mitre.org/techniques/T1196', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Create Account', + id: 'T1136', + reference: 'https://attack.mitre.org/techniques/T1136', + tactics: ['persistence'], + }, + { + name: 'Credential Dumping', + id: 'T1003', + reference: 'https://attack.mitre.org/techniques/T1003', + tactics: ['credential-access'], + }, + { + name: 'Credentials from Web Browsers', + id: 'T1503', + reference: 'https://attack.mitre.org/techniques/T1503', + tactics: ['credential-access'], + }, + { + name: 'Credentials in Files', + id: 'T1081', + reference: 'https://attack.mitre.org/techniques/T1081', + tactics: ['credential-access'], + }, + { + name: 'Credentials in Registry', + id: 'T1214', + reference: 'https://attack.mitre.org/techniques/T1214', + tactics: ['credential-access'], + }, + { + name: 'Custom Command and Control Protocol', + id: 'T1094', + reference: 'https://attack.mitre.org/techniques/T1094', + tactics: ['command-and-control'], + }, + { + name: 'Custom Cryptographic Protocol', + id: 'T1024', + reference: 'https://attack.mitre.org/techniques/T1024', + tactics: ['command-and-control'], + }, + { + name: 'DCShadow', + id: 'T1207', + reference: 'https://attack.mitre.org/techniques/T1207', + tactics: ['defense-evasion'], + }, + { + name: 'DLL Search Order Hijacking', + id: 'T1038', + reference: 'https://attack.mitre.org/techniques/T1038', + tactics: ['persistence', 'privilege-escalation', 'defense-evasion'], + }, + { + name: 'DLL Side-Loading', + id: 'T1073', + reference: 'https://attack.mitre.org/techniques/T1073', + tactics: ['defense-evasion'], + }, + { + name: 'Data Compressed', + id: 'T1002', + reference: 'https://attack.mitre.org/techniques/T1002', + tactics: ['exfiltration'], + }, + { + name: 'Data Destruction', + id: 'T1485', + reference: 'https://attack.mitre.org/techniques/T1485', + tactics: ['impact'], + }, + { + name: 'Data Encoding', + id: 'T1132', + reference: 'https://attack.mitre.org/techniques/T1132', + tactics: ['command-and-control'], + }, + { + name: 'Data Encrypted', + id: 'T1022', + reference: 'https://attack.mitre.org/techniques/T1022', + tactics: ['exfiltration'], + }, + { + name: 'Data Encrypted for Impact', + id: 'T1486', + reference: 'https://attack.mitre.org/techniques/T1486', + tactics: ['impact'], + }, + { + name: 'Data Obfuscation', + id: 'T1001', + reference: 'https://attack.mitre.org/techniques/T1001', + tactics: ['command-and-control'], + }, + { + name: 'Data Staged', + id: 'T1074', + reference: 'https://attack.mitre.org/techniques/T1074', + tactics: ['collection'], + }, + { + name: 'Data Transfer Size Limits', + id: 'T1030', + reference: 'https://attack.mitre.org/techniques/T1030', + tactics: ['exfiltration'], + }, + { + name: 'Data from Cloud Storage Object', + id: 'T1530', + reference: 'https://attack.mitre.org/techniques/T1530', + tactics: ['collection'], + }, + { + name: 'Data from Information Repositories', + id: 'T1213', + reference: 'https://attack.mitre.org/techniques/T1213', + tactics: ['collection'], + }, + { + name: 'Data from Local System', + id: 'T1005', + reference: 'https://attack.mitre.org/techniques/T1005', + tactics: ['collection'], + }, + { + name: 'Data from Network Shared Drive', + id: 'T1039', + reference: 'https://attack.mitre.org/techniques/T1039', + tactics: ['collection'], + }, + { + name: 'Data from Removable Media', + id: 'T1025', + reference: 'https://attack.mitre.org/techniques/T1025', + tactics: ['collection'], + }, + { + name: 'Defacement', + id: 'T1491', + reference: 'https://attack.mitre.org/techniques/T1491', + tactics: ['impact'], + }, + { + name: 'Deobfuscate/Decode Files or Information', + id: 'T1140', + reference: 'https://attack.mitre.org/techniques/T1140', + tactics: ['defense-evasion'], + }, + { + name: 'Disabling Security Tools', + id: 'T1089', + reference: 'https://attack.mitre.org/techniques/T1089', + tactics: ['defense-evasion'], + }, + { + name: 'Disk Content Wipe', + id: 'T1488', + reference: 'https://attack.mitre.org/techniques/T1488', + tactics: ['impact'], + }, + { + name: 'Disk Structure Wipe', + id: 'T1487', + reference: 'https://attack.mitre.org/techniques/T1487', + tactics: ['impact'], + }, + { + name: 'Domain Fronting', + id: 'T1172', + reference: 'https://attack.mitre.org/techniques/T1172', + tactics: ['command-and-control'], + }, + { + name: 'Domain Generation Algorithms', + id: 'T1483', + reference: 'https://attack.mitre.org/techniques/T1483', + tactics: ['command-and-control'], + }, + { + name: 'Domain Trust Discovery', + id: 'T1482', + reference: 'https://attack.mitre.org/techniques/T1482', + tactics: ['discovery'], + }, + { + name: 'Drive-by Compromise', + id: 'T1189', + reference: 'https://attack.mitre.org/techniques/T1189', + tactics: ['initial-access'], + }, + { + name: 'Dylib Hijacking', + id: 'T1157', + reference: 'https://attack.mitre.org/techniques/T1157', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Dynamic Data Exchange', + id: 'T1173', + reference: 'https://attack.mitre.org/techniques/T1173', + tactics: ['execution'], + }, + { + name: 'Elevated Execution with Prompt', + id: 'T1514', + reference: 'https://attack.mitre.org/techniques/T1514', + tactics: ['privilege-escalation'], + }, + { + name: 'Email Collection', + id: 'T1114', + reference: 'https://attack.mitre.org/techniques/T1114', + tactics: ['collection'], + }, + { + name: 'Emond', + id: 'T1519', + reference: 'https://attack.mitre.org/techniques/T1519', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Endpoint Denial of Service', + id: 'T1499', + reference: 'https://attack.mitre.org/techniques/T1499', + tactics: ['impact'], + }, + { + name: 'Execution Guardrails', + id: 'T1480', + reference: 'https://attack.mitre.org/techniques/T1480', + tactics: ['defense-evasion'], + }, + { + name: 'Execution through API', + id: 'T1106', + reference: 'https://attack.mitre.org/techniques/T1106', + tactics: ['execution'], + }, + { + name: 'Execution through Module Load', + id: 'T1129', + reference: 'https://attack.mitre.org/techniques/T1129', + tactics: ['execution'], + }, + { + name: 'Exfiltration Over Alternative Protocol', + id: 'T1048', + reference: 'https://attack.mitre.org/techniques/T1048', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Command and Control Channel', + id: 'T1041', + reference: 'https://attack.mitre.org/techniques/T1041', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Other Network Medium', + id: 'T1011', + reference: 'https://attack.mitre.org/techniques/T1011', + tactics: ['exfiltration'], + }, + { + name: 'Exfiltration Over Physical Medium', + id: 'T1052', + reference: 'https://attack.mitre.org/techniques/T1052', + tactics: ['exfiltration'], + }, + { + name: 'Exploit Public-Facing Application', + id: 'T1190', + reference: 'https://attack.mitre.org/techniques/T1190', + tactics: ['initial-access'], + }, + { + name: 'Exploitation for Client Execution', + id: 'T1203', + reference: 'https://attack.mitre.org/techniques/T1203', + tactics: ['execution'], + }, + { + name: 'Exploitation for Credential Access', + id: 'T1212', + reference: 'https://attack.mitre.org/techniques/T1212', + tactics: ['credential-access'], + }, + { + name: 'Exploitation for Defense Evasion', + id: 'T1211', + reference: 'https://attack.mitre.org/techniques/T1211', + tactics: ['defense-evasion'], + }, + { + name: 'Exploitation for Privilege Escalation', + id: 'T1068', + reference: 'https://attack.mitre.org/techniques/T1068', + tactics: ['privilege-escalation'], + }, + { + name: 'Exploitation of Remote Services', + id: 'T1210', + reference: 'https://attack.mitre.org/techniques/T1210', + tactics: ['lateral-movement'], + }, + { + name: 'External Remote Services', + id: 'T1133', + reference: 'https://attack.mitre.org/techniques/T1133', + tactics: ['persistence', 'initial-access'], + }, + { + name: 'Extra Window Memory Injection', + id: 'T1181', + reference: 'https://attack.mitre.org/techniques/T1181', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Fallback Channels', + id: 'T1008', + reference: 'https://attack.mitre.org/techniques/T1008', + tactics: ['command-and-control'], + }, + { + name: 'File Deletion', + id: 'T1107', + reference: 'https://attack.mitre.org/techniques/T1107', + tactics: ['defense-evasion'], + }, + { + name: 'File System Logical Offsets', + id: 'T1006', + reference: 'https://attack.mitre.org/techniques/T1006', + tactics: ['defense-evasion'], + }, + { + name: 'File System Permissions Weakness', + id: 'T1044', + reference: 'https://attack.mitre.org/techniques/T1044', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'File and Directory Discovery', + id: 'T1083', + reference: 'https://attack.mitre.org/techniques/T1083', + tactics: ['discovery'], + }, + { + name: 'File and Directory Permissions Modification', + id: 'T1222', + reference: 'https://attack.mitre.org/techniques/T1222', + tactics: ['defense-evasion'], + }, + { + name: 'Firmware Corruption', + id: 'T1495', + reference: 'https://attack.mitre.org/techniques/T1495', + tactics: ['impact'], + }, + { + name: 'Forced Authentication', + id: 'T1187', + reference: 'https://attack.mitre.org/techniques/T1187', + tactics: ['credential-access'], + }, + { + name: 'Gatekeeper Bypass', + id: 'T1144', + reference: 'https://attack.mitre.org/techniques/T1144', + tactics: ['defense-evasion'], + }, + { + name: 'Graphical User Interface', + id: 'T1061', + reference: 'https://attack.mitre.org/techniques/T1061', + tactics: ['execution'], + }, + { + name: 'Group Policy Modification', + id: 'T1484', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: ['defense-evasion'], + }, + { + name: 'HISTCONTROL', + id: 'T1148', + reference: 'https://attack.mitre.org/techniques/T1148', + tactics: ['defense-evasion'], + }, + { + name: 'Hardware Additions', + id: 'T1200', + reference: 'https://attack.mitre.org/techniques/T1200', + tactics: ['initial-access'], + }, + { + name: 'Hidden Files and Directories', + id: 'T1158', + reference: 'https://attack.mitre.org/techniques/T1158', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Hidden Users', + id: 'T1147', + reference: 'https://attack.mitre.org/techniques/T1147', + tactics: ['defense-evasion'], + }, + { + name: 'Hidden Window', + id: 'T1143', + reference: 'https://attack.mitre.org/techniques/T1143', + tactics: ['defense-evasion'], + }, + { + name: 'Hooking', + id: 'T1179', + reference: 'https://attack.mitre.org/techniques/T1179', + tactics: ['persistence', 'privilege-escalation', 'credential-access'], + }, + { + name: 'Hypervisor', + id: 'T1062', + reference: 'https://attack.mitre.org/techniques/T1062', + tactics: ['persistence'], + }, + { + name: 'Image File Execution Options Injection', + id: 'T1183', + reference: 'https://attack.mitre.org/techniques/T1183', + tactics: ['privilege-escalation', 'persistence', 'defense-evasion'], + }, + { + name: 'Implant Container Image', + id: 'T1525', + reference: 'https://attack.mitre.org/techniques/T1525', + tactics: ['persistence'], + }, + { + name: 'Indicator Blocking', + id: 'T1054', + reference: 'https://attack.mitre.org/techniques/T1054', + tactics: ['defense-evasion'], + }, + { + name: 'Indicator Removal from Tools', + id: 'T1066', + reference: 'https://attack.mitre.org/techniques/T1066', + tactics: ['defense-evasion'], + }, + { + name: 'Indicator Removal on Host', + id: 'T1070', + reference: 'https://attack.mitre.org/techniques/T1070', + tactics: ['defense-evasion'], + }, + { + name: 'Indirect Command Execution', + id: 'T1202', + reference: 'https://attack.mitre.org/techniques/T1202', + tactics: ['defense-evasion'], + }, + { + name: 'Inhibit System Recovery', + id: 'T1490', + reference: 'https://attack.mitre.org/techniques/T1490', + tactics: ['impact'], + }, + { + name: 'Input Capture', + id: 'T1056', + reference: 'https://attack.mitre.org/techniques/T1056', + tactics: ['collection', 'credential-access'], + }, + { + name: 'Input Prompt', + id: 'T1141', + reference: 'https://attack.mitre.org/techniques/T1141', + tactics: ['credential-access'], + }, + { + name: 'Install Root Certificate', + id: 'T1130', + reference: 'https://attack.mitre.org/techniques/T1130', + tactics: ['defense-evasion'], + }, + { + name: 'InstallUtil', + id: 'T1118', + reference: 'https://attack.mitre.org/techniques/T1118', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Internal Spearphishing', + id: 'T1534', + reference: 'https://attack.mitre.org/techniques/T1534', + tactics: ['lateral-movement'], + }, + { + name: 'Kerberoasting', + id: 'T1208', + reference: 'https://attack.mitre.org/techniques/T1208', + tactics: ['credential-access'], + }, + { + name: 'Kernel Modules and Extensions', + id: 'T1215', + reference: 'https://attack.mitre.org/techniques/T1215', + tactics: ['persistence'], + }, + { + name: 'Keychain', + id: 'T1142', + reference: 'https://attack.mitre.org/techniques/T1142', + tactics: ['credential-access'], + }, + { + name: 'LC_LOAD_DYLIB Addition', + id: 'T1161', + reference: 'https://attack.mitre.org/techniques/T1161', + tactics: ['persistence'], + }, + { + name: 'LC_MAIN Hijacking', + id: 'T1149', + reference: 'https://attack.mitre.org/techniques/T1149', + tactics: ['defense-evasion'], + }, + { + name: 'LLMNR/NBT-NS Poisoning and Relay', + id: 'T1171', + reference: 'https://attack.mitre.org/techniques/T1171', + tactics: ['credential-access'], + }, + { + name: 'LSASS Driver', + id: 'T1177', + reference: 'https://attack.mitre.org/techniques/T1177', + tactics: ['execution', 'persistence'], + }, + { + name: 'Launch Agent', + id: 'T1159', + reference: 'https://attack.mitre.org/techniques/T1159', + tactics: ['persistence'], + }, + { + name: 'Launch Daemon', + id: 'T1160', + reference: 'https://attack.mitre.org/techniques/T1160', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Launchctl', + id: 'T1152', + reference: 'https://attack.mitre.org/techniques/T1152', + tactics: ['defense-evasion', 'execution', 'persistence'], + }, + { + name: 'Local Job Scheduling', + id: 'T1168', + reference: 'https://attack.mitre.org/techniques/T1168', + tactics: ['persistence', 'execution'], + }, + { + name: 'Login Item', + id: 'T1162', + reference: 'https://attack.mitre.org/techniques/T1162', + tactics: ['persistence'], + }, + { + name: 'Logon Scripts', + id: 'T1037', + reference: 'https://attack.mitre.org/techniques/T1037', + tactics: ['lateral-movement', 'persistence'], + }, + { + name: 'Man in the Browser', + id: 'T1185', + reference: 'https://attack.mitre.org/techniques/T1185', + tactics: ['collection'], + }, + { + name: 'Masquerading', + id: 'T1036', + reference: 'https://attack.mitre.org/techniques/T1036', + tactics: ['defense-evasion'], + }, + { + name: 'Modify Existing Service', + id: 'T1031', + reference: 'https://attack.mitre.org/techniques/T1031', + tactics: ['persistence'], + }, + { + name: 'Modify Registry', + id: 'T1112', + reference: 'https://attack.mitre.org/techniques/T1112', + tactics: ['defense-evasion'], + }, + { + name: 'Mshta', + id: 'T1170', + reference: 'https://attack.mitre.org/techniques/T1170', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Multi-Stage Channels', + id: 'T1104', + reference: 'https://attack.mitre.org/techniques/T1104', + tactics: ['command-and-control'], + }, + { + name: 'Multi-hop Proxy', + id: 'T1188', + reference: 'https://attack.mitre.org/techniques/T1188', + tactics: ['command-and-control'], + }, + { + name: 'Multiband Communication', + id: 'T1026', + reference: 'https://attack.mitre.org/techniques/T1026', + tactics: ['command-and-control'], + }, + { + name: 'Multilayer Encryption', + id: 'T1079', + reference: 'https://attack.mitre.org/techniques/T1079', + tactics: ['command-and-control'], + }, + { + name: 'NTFS File Attributes', + id: 'T1096', + reference: 'https://attack.mitre.org/techniques/T1096', + tactics: ['defense-evasion'], + }, + { + name: 'Netsh Helper DLL', + id: 'T1128', + reference: 'https://attack.mitre.org/techniques/T1128', + tactics: ['persistence'], + }, + { + name: 'Network Denial of Service', + id: 'T1498', + reference: 'https://attack.mitre.org/techniques/T1498', + tactics: ['impact'], + }, + { + name: 'Network Service Scanning', + id: 'T1046', + reference: 'https://attack.mitre.org/techniques/T1046', + tactics: ['discovery'], + }, + { + name: 'Network Share Connection Removal', + id: 'T1126', + reference: 'https://attack.mitre.org/techniques/T1126', + tactics: ['defense-evasion'], + }, + { + name: 'Network Share Discovery', + id: 'T1135', + reference: 'https://attack.mitre.org/techniques/T1135', + tactics: ['discovery'], + }, + { + name: 'Network Sniffing', + id: 'T1040', + reference: 'https://attack.mitre.org/techniques/T1040', + tactics: ['credential-access', 'discovery'], + }, + { + name: 'New Service', + id: 'T1050', + reference: 'https://attack.mitre.org/techniques/T1050', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Obfuscated Files or Information', + id: 'T1027', + reference: 'https://attack.mitre.org/techniques/T1027', + tactics: ['defense-evasion'], + }, + { + name: 'Office Application Startup', + id: 'T1137', + reference: 'https://attack.mitre.org/techniques/T1137', + tactics: ['persistence'], + }, + { + name: 'Parent PID Spoofing', + id: 'T1502', + reference: 'https://attack.mitre.org/techniques/T1502', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Pass the Hash', + id: 'T1075', + reference: 'https://attack.mitre.org/techniques/T1075', + tactics: ['lateral-movement'], + }, + { + name: 'Pass the Ticket', + id: 'T1097', + reference: 'https://attack.mitre.org/techniques/T1097', + tactics: ['lateral-movement'], + }, + { + name: 'Password Filter DLL', + id: 'T1174', + reference: 'https://attack.mitre.org/techniques/T1174', + tactics: ['credential-access'], + }, + { + name: 'Password Policy Discovery', + id: 'T1201', + reference: 'https://attack.mitre.org/techniques/T1201', + tactics: ['discovery'], + }, + { + name: 'Path Interception', + id: 'T1034', + reference: 'https://attack.mitre.org/techniques/T1034', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Peripheral Device Discovery', + id: 'T1120', + reference: 'https://attack.mitre.org/techniques/T1120', + tactics: ['discovery'], + }, + { + name: 'Permission Groups Discovery', + id: 'T1069', + reference: 'https://attack.mitre.org/techniques/T1069', + tactics: ['discovery'], + }, + { + name: 'Plist Modification', + id: 'T1150', + reference: 'https://attack.mitre.org/techniques/T1150', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation'], + }, + { + name: 'Port Knocking', + id: 'T1205', + reference: 'https://attack.mitre.org/techniques/T1205', + tactics: ['defense-evasion', 'persistence', 'command-and-control'], + }, + { + name: 'Port Monitors', + id: 'T1013', + reference: 'https://attack.mitre.org/techniques/T1013', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'PowerShell', + id: 'T1086', + reference: 'https://attack.mitre.org/techniques/T1086', + tactics: ['execution'], + }, + { + name: 'PowerShell Profile', + id: 'T1504', + reference: 'https://attack.mitre.org/techniques/T1504', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Private Keys', + id: 'T1145', + reference: 'https://attack.mitre.org/techniques/T1145', + tactics: ['credential-access'], + }, + { + name: 'Process Discovery', + id: 'T1057', + reference: 'https://attack.mitre.org/techniques/T1057', + tactics: ['discovery'], + }, + { + name: 'Process Doppelgänging', + id: 'T1186', + reference: 'https://attack.mitre.org/techniques/T1186', + tactics: ['defense-evasion'], + }, + { + name: 'Process Hollowing', + id: 'T1093', + reference: 'https://attack.mitre.org/techniques/T1093', + tactics: ['defense-evasion'], + }, + { + name: 'Process Injection', + id: 'T1055', + reference: 'https://attack.mitre.org/techniques/T1055', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Query Registry', + id: 'T1012', + reference: 'https://attack.mitre.org/techniques/T1012', + tactics: ['discovery'], + }, + { + name: 'Rc.common', + id: 'T1163', + reference: 'https://attack.mitre.org/techniques/T1163', + tactics: ['persistence'], + }, + { + name: 'Re-opened Applications', + id: 'T1164', + reference: 'https://attack.mitre.org/techniques/T1164', + tactics: ['persistence'], + }, + { + name: 'Redundant Access', + id: 'T1108', + reference: 'https://attack.mitre.org/techniques/T1108', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'Registry Run Keys / Startup Folder', + id: 'T1060', + reference: 'https://attack.mitre.org/techniques/T1060', + tactics: ['persistence'], + }, + { + name: 'Regsvcs/Regasm', + id: 'T1121', + reference: 'https://attack.mitre.org/techniques/T1121', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Regsvr32', + id: 'T1117', + reference: 'https://attack.mitre.org/techniques/T1117', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Remote Access Tools', + id: 'T1219', + reference: 'https://attack.mitre.org/techniques/T1219', + tactics: ['command-and-control'], + }, + { + name: 'Remote Desktop Protocol', + id: 'T1076', + reference: 'https://attack.mitre.org/techniques/T1076', + tactics: ['lateral-movement'], + }, + { + name: 'Remote File Copy', + id: 'T1105', + reference: 'https://attack.mitre.org/techniques/T1105', + tactics: ['command-and-control', 'lateral-movement'], + }, + { + name: 'Remote Services', + id: 'T1021', + reference: 'https://attack.mitre.org/techniques/T1021', + tactics: ['lateral-movement'], + }, + { + name: 'Remote System Discovery', + id: 'T1018', + reference: 'https://attack.mitre.org/techniques/T1018', + tactics: ['discovery'], + }, + { + name: 'Replication Through Removable Media', + id: 'T1091', + reference: 'https://attack.mitre.org/techniques/T1091', + tactics: ['lateral-movement', 'initial-access'], + }, + { + name: 'Resource Hijacking', + id: 'T1496', + reference: 'https://attack.mitre.org/techniques/T1496', + tactics: ['impact'], + }, + { + name: 'Revert Cloud Instance', + id: 'T1536', + reference: 'https://attack.mitre.org/techniques/T1536', + tactics: ['defense-evasion'], + }, + { + name: 'Rootkit', + id: 'T1014', + reference: 'https://attack.mitre.org/techniques/T1014', + tactics: ['defense-evasion'], + }, + { + name: 'Rundll32', + id: 'T1085', + reference: 'https://attack.mitre.org/techniques/T1085', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Runtime Data Manipulation', + id: 'T1494', + reference: 'https://attack.mitre.org/techniques/T1494', + tactics: ['impact'], + }, + { + name: 'SID-History Injection', + id: 'T1178', + reference: 'https://attack.mitre.org/techniques/T1178', + tactics: ['privilege-escalation'], + }, + { + name: 'SIP and Trust Provider Hijacking', + id: 'T1198', + reference: 'https://attack.mitre.org/techniques/T1198', + tactics: ['defense-evasion', 'persistence'], + }, + { + name: 'SSH Hijacking', + id: 'T1184', + reference: 'https://attack.mitre.org/techniques/T1184', + tactics: ['lateral-movement'], + }, + { + name: 'Scheduled Task', + id: 'T1053', + reference: 'https://attack.mitre.org/techniques/T1053', + tactics: ['execution', 'persistence', 'privilege-escalation'], + }, + { + name: 'Scheduled Transfer', + id: 'T1029', + reference: 'https://attack.mitre.org/techniques/T1029', + tactics: ['exfiltration'], + }, + { + name: 'Screen Capture', + id: 'T1113', + reference: 'https://attack.mitre.org/techniques/T1113', + tactics: ['collection'], + }, + { + name: 'Screensaver', + id: 'T1180', + reference: 'https://attack.mitre.org/techniques/T1180', + tactics: ['persistence'], + }, + { + name: 'Scripting', + id: 'T1064', + reference: 'https://attack.mitre.org/techniques/T1064', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Security Software Discovery', + id: 'T1063', + reference: 'https://attack.mitre.org/techniques/T1063', + tactics: ['discovery'], + }, + { + name: 'Security Support Provider', + id: 'T1101', + reference: 'https://attack.mitre.org/techniques/T1101', + tactics: ['persistence'], + }, + { + name: 'Securityd Memory', + id: 'T1167', + reference: 'https://attack.mitre.org/techniques/T1167', + tactics: ['credential-access'], + }, + { + name: 'Server Software Component', + id: 'T1505', + reference: 'https://attack.mitre.org/techniques/T1505', + tactics: ['persistence'], + }, + { + name: 'Service Execution', + id: 'T1035', + reference: 'https://attack.mitre.org/techniques/T1035', + tactics: ['execution'], + }, + { + name: 'Service Registry Permissions Weakness', + id: 'T1058', + reference: 'https://attack.mitre.org/techniques/T1058', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Service Stop', + id: 'T1489', + reference: 'https://attack.mitre.org/techniques/T1489', + tactics: ['impact'], + }, + { + name: 'Setuid and Setgid', + id: 'T1166', + reference: 'https://attack.mitre.org/techniques/T1166', + tactics: ['privilege-escalation', 'persistence'], + }, + { + name: 'Shared Webroot', + id: 'T1051', + reference: 'https://attack.mitre.org/techniques/T1051', + tactics: ['lateral-movement'], + }, + { + name: 'Shortcut Modification', + id: 'T1023', + reference: 'https://attack.mitre.org/techniques/T1023', + tactics: ['persistence'], + }, + { + name: 'Signed Binary Proxy Execution', + id: 'T1218', + reference: 'https://attack.mitre.org/techniques/T1218', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Signed Script Proxy Execution', + id: 'T1216', + reference: 'https://attack.mitre.org/techniques/T1216', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Software Discovery', + id: 'T1518', + reference: 'https://attack.mitre.org/techniques/T1518', + tactics: ['discovery'], + }, + { + name: 'Software Packing', + id: 'T1045', + reference: 'https://attack.mitre.org/techniques/T1045', + tactics: ['defense-evasion'], + }, + { + name: 'Source', + id: 'T1153', + reference: 'https://attack.mitre.org/techniques/T1153', + tactics: ['execution'], + }, + { + name: 'Space after Filename', + id: 'T1151', + reference: 'https://attack.mitre.org/techniques/T1151', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Spearphishing Attachment', + id: 'T1193', + reference: 'https://attack.mitre.org/techniques/T1193', + tactics: ['initial-access'], + }, + { + name: 'Spearphishing Link', + id: 'T1192', + reference: 'https://attack.mitre.org/techniques/T1192', + tactics: ['initial-access'], + }, + { + name: 'Spearphishing via Service', + id: 'T1194', + reference: 'https://attack.mitre.org/techniques/T1194', + tactics: ['initial-access'], + }, + { + name: 'Standard Application Layer Protocol', + id: 'T1071', + reference: 'https://attack.mitre.org/techniques/T1071', + tactics: ['command-and-control'], + }, + { + name: 'Standard Cryptographic Protocol', + id: 'T1032', + reference: 'https://attack.mitre.org/techniques/T1032', + tactics: ['command-and-control'], + }, + { + name: 'Standard Non-Application Layer Protocol', + id: 'T1095', + reference: 'https://attack.mitre.org/techniques/T1095', + tactics: ['command-and-control'], + }, + { + name: 'Startup Items', + id: 'T1165', + reference: 'https://attack.mitre.org/techniques/T1165', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Steal Application Access Token', + id: 'T1528', + reference: 'https://attack.mitre.org/techniques/T1528', + tactics: ['credential-access'], + }, + { + name: 'Steal Web Session Cookie', + id: 'T1539', + reference: 'https://attack.mitre.org/techniques/T1539', + tactics: ['credential-access'], + }, + { + name: 'Stored Data Manipulation', + id: 'T1492', + reference: 'https://attack.mitre.org/techniques/T1492', + tactics: ['impact'], + }, + { + name: 'Sudo', + id: 'T1169', + reference: 'https://attack.mitre.org/techniques/T1169', + tactics: ['privilege-escalation'], + }, + { + name: 'Sudo Caching', + id: 'T1206', + reference: 'https://attack.mitre.org/techniques/T1206', + tactics: ['privilege-escalation'], + }, + { + name: 'Supply Chain Compromise', + id: 'T1195', + reference: 'https://attack.mitre.org/techniques/T1195', + tactics: ['initial-access'], + }, + { + name: 'System Firmware', + id: 'T1019', + reference: 'https://attack.mitre.org/techniques/T1019', + tactics: ['persistence'], + }, + { + name: 'System Information Discovery', + id: 'T1082', + reference: 'https://attack.mitre.org/techniques/T1082', + tactics: ['discovery'], + }, + { + name: 'System Network Configuration Discovery', + id: 'T1016', + reference: 'https://attack.mitre.org/techniques/T1016', + tactics: ['discovery'], + }, + { + name: 'System Network Connections Discovery', + id: 'T1049', + reference: 'https://attack.mitre.org/techniques/T1049', + tactics: ['discovery'], + }, + { + name: 'System Owner/User Discovery', + id: 'T1033', + reference: 'https://attack.mitre.org/techniques/T1033', + tactics: ['discovery'], + }, + { + name: 'System Service Discovery', + id: 'T1007', + reference: 'https://attack.mitre.org/techniques/T1007', + tactics: ['discovery'], + }, + { + name: 'System Shutdown/Reboot', + id: 'T1529', + reference: 'https://attack.mitre.org/techniques/T1529', + tactics: ['impact'], + }, + { + name: 'System Time Discovery', + id: 'T1124', + reference: 'https://attack.mitre.org/techniques/T1124', + tactics: ['discovery'], + }, + { + name: 'Systemd Service', + id: 'T1501', + reference: 'https://attack.mitre.org/techniques/T1501', + tactics: ['persistence'], + }, + { + name: 'Taint Shared Content', + id: 'T1080', + reference: 'https://attack.mitre.org/techniques/T1080', + tactics: ['lateral-movement'], + }, + { + name: 'Template Injection', + id: 'T1221', + reference: 'https://attack.mitre.org/techniques/T1221', + tactics: ['defense-evasion'], + }, + { + name: 'Third-party Software', + id: 'T1072', + reference: 'https://attack.mitre.org/techniques/T1072', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Time Providers', + id: 'T1209', + reference: 'https://attack.mitre.org/techniques/T1209', + tactics: ['persistence'], + }, + { + name: 'Timestomp', + id: 'T1099', + reference: 'https://attack.mitre.org/techniques/T1099', + tactics: ['defense-evasion'], + }, + { + name: 'Transfer Data to Cloud Account', + id: 'T1537', + reference: 'https://attack.mitre.org/techniques/T1537', + tactics: ['exfiltration'], + }, + { + name: 'Transmitted Data Manipulation', + id: 'T1493', + reference: 'https://attack.mitre.org/techniques/T1493', + tactics: ['impact'], + }, + { + name: 'Trap', + id: 'T1154', + reference: 'https://attack.mitre.org/techniques/T1154', + tactics: ['execution', 'persistence'], + }, + { + name: 'Trusted Developer Utilities', + id: 'T1127', + reference: 'https://attack.mitre.org/techniques/T1127', + tactics: ['defense-evasion', 'execution'], + }, + { + name: 'Trusted Relationship', + id: 'T1199', + reference: 'https://attack.mitre.org/techniques/T1199', + tactics: ['initial-access'], + }, + { + name: 'Two-Factor Authentication Interception', + id: 'T1111', + reference: 'https://attack.mitre.org/techniques/T1111', + tactics: ['credential-access'], + }, + { + name: 'Uncommonly Used Port', + id: 'T1065', + reference: 'https://attack.mitre.org/techniques/T1065', + tactics: ['command-and-control'], + }, + { + name: 'Unused/Unsupported Cloud Regions', + id: 'T1535', + reference: 'https://attack.mitre.org/techniques/T1535', + tactics: ['defense-evasion'], + }, + { + name: 'User Execution', + id: 'T1204', + reference: 'https://attack.mitre.org/techniques/T1204', + tactics: ['execution'], + }, + { + name: 'Valid Accounts', + id: 'T1078', + reference: 'https://attack.mitre.org/techniques/T1078', + tactics: ['defense-evasion', 'persistence', 'privilege-escalation', 'initial-access'], + }, + { + name: 'Video Capture', + id: 'T1125', + reference: 'https://attack.mitre.org/techniques/T1125', + tactics: ['collection'], + }, + { + name: 'Virtualization/Sandbox Evasion', + id: 'T1497', + reference: 'https://attack.mitre.org/techniques/T1497', + tactics: ['defense-evasion', 'discovery'], + }, + { + name: 'Web Service', + id: 'T1102', + reference: 'https://attack.mitre.org/techniques/T1102', + tactics: ['command-and-control', 'defense-evasion'], + }, + { + name: 'Web Session Cookie', + id: 'T1506', + reference: 'https://attack.mitre.org/techniques/T1506', + tactics: ['defense-evasion', 'lateral-movement'], + }, + { + name: 'Web Shell', + id: 'T1100', + reference: 'https://attack.mitre.org/techniques/T1100', + tactics: ['persistence', 'privilege-escalation'], + }, + { + name: 'Windows Admin Shares', + id: 'T1077', + reference: 'https://attack.mitre.org/techniques/T1077', + tactics: ['lateral-movement'], + }, + { + name: 'Windows Management Instrumentation', + id: 'T1047', + reference: 'https://attack.mitre.org/techniques/T1047', + tactics: ['execution'], + }, + { + name: 'Windows Management Instrumentation Event Subscription', + id: 'T1084', + reference: 'https://attack.mitre.org/techniques/T1084', + tactics: ['persistence'], + }, + { + name: 'Windows Remote Management', + id: 'T1028', + reference: 'https://attack.mitre.org/techniques/T1028', + tactics: ['execution', 'lateral-movement'], + }, + { + name: 'Winlogon Helper DLL', + id: 'T1004', + reference: 'https://attack.mitre.org/techniques/T1004', + tactics: ['persistence'], + }, + { + name: 'XSL Script Processing', + id: 'T1220', + reference: 'https://attack.mitre.org/techniques/T1220', + tactics: ['defense-evasion', 'execution'], + }, +]; + +export const techniquesOptions: MitreTechniquesOptions[] = [ + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bashProfileAndBashrcDescription', + { defaultMessage: '.bash_profile and .bashrc (T1156)' } + ), + id: 'T1156', + name: '.bash_profile and .bashrc', + reference: 'https://attack.mitre.org/techniques/T1156', + tactics: 'persistence', + value: 'bashProfileAndBashrc', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accessTokenManipulationDescription', + { defaultMessage: 'Access Token Manipulation (T1134)' } + ), + id: 'T1134', + name: 'Access Token Manipulation', + reference: 'https://attack.mitre.org/techniques/T1134', + tactics: 'defense-evasion,privilege-escalation', + value: 'accessTokenManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accessibilityFeaturesDescription', + { defaultMessage: 'Accessibility Features (T1015)' } + ), + id: 'T1015', + name: 'Accessibility Features', + reference: 'https://attack.mitre.org/techniques/T1015', + tactics: 'persistence,privilege-escalation', + value: 'accessibilityFeatures', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountAccessRemovalDescription', + { defaultMessage: 'Account Access Removal (T1531)' } + ), + id: 'T1531', + name: 'Account Access Removal', + reference: 'https://attack.mitre.org/techniques/T1531', + tactics: 'impact', + value: 'accountAccessRemoval', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountDiscoveryDescription', + { defaultMessage: 'Account Discovery (T1087)' } + ), + id: 'T1087', + name: 'Account Discovery', + reference: 'https://attack.mitre.org/techniques/T1087', + tactics: 'discovery', + value: 'accountDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.accountManipulationDescription', + { defaultMessage: 'Account Manipulation (T1098)' } + ), + id: 'T1098', + name: 'Account Manipulation', + reference: 'https://attack.mitre.org/techniques/T1098', + tactics: 'credential-access,persistence', + value: 'accountManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appCertDlLsDescription', + { defaultMessage: 'AppCert DLLs (T1182)' } + ), + id: 'T1182', + name: 'AppCert DLLs', + reference: 'https://attack.mitre.org/techniques/T1182', + tactics: 'persistence,privilege-escalation', + value: 'appCertDlLs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appInitDlLsDescription', + { defaultMessage: 'AppInit DLLs (T1103)' } + ), + id: 'T1103', + name: 'AppInit DLLs', + reference: 'https://attack.mitre.org/techniques/T1103', + tactics: 'persistence,privilege-escalation', + value: 'appInitDlLs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.appleScriptDescription', + { defaultMessage: 'AppleScript (T1155)' } + ), + id: 'T1155', + name: 'AppleScript', + reference: 'https://attack.mitre.org/techniques/T1155', + tactics: 'execution,lateral-movement', + value: 'appleScript', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationAccessTokenDescription', + { defaultMessage: 'Application Access Token (T1527)' } + ), + id: 'T1527', + name: 'Application Access Token', + reference: 'https://attack.mitre.org/techniques/T1527', + tactics: 'defense-evasion,lateral-movement', + value: 'applicationAccessToken', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationDeploymentSoftwareDescription', + { defaultMessage: 'Application Deployment Software (T1017)' } + ), + id: 'T1017', + name: 'Application Deployment Software', + reference: 'https://attack.mitre.org/techniques/T1017', + tactics: 'lateral-movement', + value: 'applicationDeploymentSoftware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationShimmingDescription', + { defaultMessage: 'Application Shimming (T1138)' } + ), + id: 'T1138', + name: 'Application Shimming', + reference: 'https://attack.mitre.org/techniques/T1138', + tactics: 'persistence,privilege-escalation', + value: 'applicationShimming', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.applicationWindowDiscoveryDescription', + { defaultMessage: 'Application Window Discovery (T1010)' } + ), + id: 'T1010', + name: 'Application Window Discovery', + reference: 'https://attack.mitre.org/techniques/T1010', + tactics: 'discovery', + value: 'applicationWindowDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.audioCaptureDescription', + { defaultMessage: 'Audio Capture (T1123)' } + ), + id: 'T1123', + name: 'Audio Capture', + reference: 'https://attack.mitre.org/techniques/T1123', + tactics: 'collection', + value: 'audioCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.authenticationPackageDescription', + { defaultMessage: 'Authentication Package (T1131)' } + ), + id: 'T1131', + name: 'Authentication Package', + reference: 'https://attack.mitre.org/techniques/T1131', + tactics: 'persistence', + value: 'authenticationPackage', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedCollectionDescription', + { defaultMessage: 'Automated Collection (T1119)' } + ), + id: 'T1119', + name: 'Automated Collection', + reference: 'https://attack.mitre.org/techniques/T1119', + tactics: 'collection', + value: 'automatedCollection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.automatedExfiltrationDescription', + { defaultMessage: 'Automated Exfiltration (T1020)' } + ), + id: 'T1020', + name: 'Automated Exfiltration', + reference: 'https://attack.mitre.org/techniques/T1020', + tactics: 'exfiltration', + value: 'automatedExfiltration', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bitsJobsDescription', { + defaultMessage: 'BITS Jobs (T1197)', + }), + id: 'T1197', + name: 'BITS Jobs', + reference: 'https://attack.mitre.org/techniques/T1197', + tactics: 'defense-evasion,persistence', + value: 'bitsJobs', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bashHistoryDescription', + { defaultMessage: 'Bash History (T1139)' } + ), + id: 'T1139', + name: 'Bash History', + reference: 'https://attack.mitre.org/techniques/T1139', + tactics: 'credential-access', + value: 'bashHistory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.binaryPaddingDescription', + { defaultMessage: 'Binary Padding (T1009)' } + ), + id: 'T1009', + name: 'Binary Padding', + reference: 'https://attack.mitre.org/techniques/T1009', + tactics: 'defense-evasion', + value: 'binaryPadding', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.bootkitDescription', { + defaultMessage: 'Bootkit (T1067)', + }), + id: 'T1067', + name: 'Bootkit', + reference: 'https://attack.mitre.org/techniques/T1067', + tactics: 'persistence', + value: 'bootkit', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.browserBookmarkDiscoveryDescription', + { defaultMessage: 'Browser Bookmark Discovery (T1217)' } + ), + id: 'T1217', + name: 'Browser Bookmark Discovery', + reference: 'https://attack.mitre.org/techniques/T1217', + tactics: 'discovery', + value: 'browserBookmarkDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.browserExtensionsDescription', + { defaultMessage: 'Browser Extensions (T1176)' } + ), + id: 'T1176', + name: 'Browser Extensions', + reference: 'https://attack.mitre.org/techniques/T1176', + tactics: 'persistence', + value: 'browserExtensions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bruteForceDescription', + { defaultMessage: 'Brute Force (T1110)' } + ), + id: 'T1110', + name: 'Brute Force', + reference: 'https://attack.mitre.org/techniques/T1110', + tactics: 'credential-access', + value: 'bruteForce', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.bypassUserAccountControlDescription', + { defaultMessage: 'Bypass User Account Control (T1088)' } + ), + id: 'T1088', + name: 'Bypass User Account Control', + reference: 'https://attack.mitre.org/techniques/T1088', + tactics: 'defense-evasion,privilege-escalation', + value: 'bypassUserAccountControl', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.cmstpDescription', { + defaultMessage: 'CMSTP (T1191)', + }), + id: 'T1191', + name: 'CMSTP', + reference: 'https://attack.mitre.org/techniques/T1191', + tactics: 'defense-evasion,execution', + value: 'cmstp', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.changeDefaultFileAssociationDescription', + { defaultMessage: 'Change Default File Association (T1042)' } + ), + id: 'T1042', + name: 'Change Default File Association', + reference: 'https://attack.mitre.org/techniques/T1042', + tactics: 'persistence', + value: 'changeDefaultFileAssociation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.clearCommandHistoryDescription', + { defaultMessage: 'Clear Command History (T1146)' } + ), + id: 'T1146', + name: 'Clear Command History', + reference: 'https://attack.mitre.org/techniques/T1146', + tactics: 'defense-evasion', + value: 'clearCommandHistory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.clipboardDataDescription', + { defaultMessage: 'Clipboard Data (T1115)' } + ), + id: 'T1115', + name: 'Clipboard Data', + reference: 'https://attack.mitre.org/techniques/T1115', + tactics: 'collection', + value: 'clipboardData', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudInstanceMetadataApiDescription', + { defaultMessage: 'Cloud Instance Metadata API (T1522)' } + ), + id: 'T1522', + name: 'Cloud Instance Metadata API', + reference: 'https://attack.mitre.org/techniques/T1522', + tactics: 'credential-access', + value: 'cloudInstanceMetadataApi', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDashboardDescription', + { defaultMessage: 'Cloud Service Dashboard (T1538)' } + ), + id: 'T1538', + name: 'Cloud Service Dashboard', + reference: 'https://attack.mitre.org/techniques/T1538', + tactics: 'discovery', + value: 'cloudServiceDashboard', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.cloudServiceDiscoveryDescription', + { defaultMessage: 'Cloud Service Discovery (T1526)' } + ), + id: 'T1526', + name: 'Cloud Service Discovery', + reference: 'https://attack.mitre.org/techniques/T1526', + tactics: 'discovery', + value: 'cloudServiceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.codeSigningDescription', + { defaultMessage: 'Code Signing (T1116)' } + ), + id: 'T1116', + name: 'Code Signing', + reference: 'https://attack.mitre.org/techniques/T1116', + tactics: 'defense-evasion', + value: 'codeSigning', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.commandLineInterfaceDescription', + { defaultMessage: 'Command-Line Interface (T1059)' } + ), + id: 'T1059', + name: 'Command-Line Interface', + reference: 'https://attack.mitre.org/techniques/T1059', + tactics: 'execution', + value: 'commandLineInterface', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.commonlyUsedPortDescription', + { defaultMessage: 'Commonly Used Port (T1043)' } + ), + id: 'T1043', + name: 'Commonly Used Port', + reference: 'https://attack.mitre.org/techniques/T1043', + tactics: 'command-and-control', + value: 'commonlyUsedPort', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.communicationThroughRemovableMediaDescription', + { defaultMessage: 'Communication Through Removable Media (T1092)' } + ), + id: 'T1092', + name: 'Communication Through Removable Media', + reference: 'https://attack.mitre.org/techniques/T1092', + tactics: 'command-and-control', + value: 'communicationThroughRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.compileAfterDeliveryDescription', + { defaultMessage: 'Compile After Delivery (T1500)' } + ), + id: 'T1500', + name: 'Compile After Delivery', + reference: 'https://attack.mitre.org/techniques/T1500', + tactics: 'defense-evasion', + value: 'compileAfterDelivery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.compiledHtmlFileDescription', + { defaultMessage: 'Compiled HTML File (T1223)' } + ), + id: 'T1223', + name: 'Compiled HTML File', + reference: 'https://attack.mitre.org/techniques/T1223', + tactics: 'defense-evasion,execution', + value: 'compiledHtmlFile', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentFirmwareDescription', + { defaultMessage: 'Component Firmware (T1109)' } + ), + id: 'T1109', + name: 'Component Firmware', + reference: 'https://attack.mitre.org/techniques/T1109', + tactics: 'defense-evasion,persistence', + value: 'componentFirmware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelHijackingDescription', + { defaultMessage: 'Component Object Model Hijacking (T1122)' } + ), + id: 'T1122', + name: 'Component Object Model Hijacking', + reference: 'https://attack.mitre.org/techniques/T1122', + tactics: 'defense-evasion,persistence', + value: 'componentObjectModelHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.componentObjectModelAndDistributedComDescription', + { defaultMessage: 'Component Object Model and Distributed COM (T1175)' } + ), + id: 'T1175', + name: 'Component Object Model and Distributed COM', + reference: 'https://attack.mitre.org/techniques/T1175', + tactics: 'lateral-movement,execution', + value: 'componentObjectModelAndDistributedCom', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.connectionProxyDescription', + { defaultMessage: 'Connection Proxy (T1090)' } + ), + id: 'T1090', + name: 'Connection Proxy', + reference: 'https://attack.mitre.org/techniques/T1090', + tactics: 'command-and-control,defense-evasion', + value: 'connectionProxy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.controlPanelItemsDescription', + { defaultMessage: 'Control Panel Items (T1196)' } + ), + id: 'T1196', + name: 'Control Panel Items', + reference: 'https://attack.mitre.org/techniques/T1196', + tactics: 'defense-evasion,execution', + value: 'controlPanelItems', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.createAccountDescription', + { defaultMessage: 'Create Account (T1136)' } + ), + id: 'T1136', + name: 'Create Account', + reference: 'https://attack.mitre.org/techniques/T1136', + tactics: 'persistence', + value: 'createAccount', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialDumpingDescription', + { defaultMessage: 'Credential Dumping (T1003)' } + ), + id: 'T1003', + name: 'Credential Dumping', + reference: 'https://attack.mitre.org/techniques/T1003', + tactics: 'credential-access', + value: 'credentialDumping', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsFromWebBrowsersDescription', + { defaultMessage: 'Credentials from Web Browsers (T1503)' } + ), + id: 'T1503', + name: 'Credentials from Web Browsers', + reference: 'https://attack.mitre.org/techniques/T1503', + tactics: 'credential-access', + value: 'credentialsFromWebBrowsers', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInFilesDescription', + { defaultMessage: 'Credentials in Files (T1081)' } + ), + id: 'T1081', + name: 'Credentials in Files', + reference: 'https://attack.mitre.org/techniques/T1081', + tactics: 'credential-access', + value: 'credentialsInFiles', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.credentialsInRegistryDescription', + { defaultMessage: 'Credentials in Registry (T1214)' } + ), + id: 'T1214', + name: 'Credentials in Registry', + reference: 'https://attack.mitre.org/techniques/T1214', + tactics: 'credential-access', + value: 'credentialsInRegistry', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.customCommandAndControlProtocolDescription', + { defaultMessage: 'Custom Command and Control Protocol (T1094)' } + ), + id: 'T1094', + name: 'Custom Command and Control Protocol', + reference: 'https://attack.mitre.org/techniques/T1094', + tactics: 'command-and-control', + value: 'customCommandAndControlProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.customCryptographicProtocolDescription', + { defaultMessage: 'Custom Cryptographic Protocol (T1024)' } + ), + id: 'T1024', + name: 'Custom Cryptographic Protocol', + reference: 'https://attack.mitre.org/techniques/T1024', + tactics: 'command-and-control', + value: 'customCryptographicProtocol', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.dcShadowDescription', { + defaultMessage: 'DCShadow (T1207)', + }), + id: 'T1207', + name: 'DCShadow', + reference: 'https://attack.mitre.org/techniques/T1207', + tactics: 'defense-evasion', + value: 'dcShadow', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSearchOrderHijackingDescription', + { defaultMessage: 'DLL Search Order Hijacking (T1038)' } + ), + id: 'T1038', + name: 'DLL Search Order Hijacking', + reference: 'https://attack.mitre.org/techniques/T1038', + tactics: 'persistence,privilege-escalation,defense-evasion', + value: 'dllSearchOrderHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dllSideLoadingDescription', + { defaultMessage: 'DLL Side-Loading (T1073)' } + ), + id: 'T1073', + name: 'DLL Side-Loading', + reference: 'https://attack.mitre.org/techniques/T1073', + tactics: 'defense-evasion', + value: 'dllSideLoading', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataCompressedDescription', + { defaultMessage: 'Data Compressed (T1002)' } + ), + id: 'T1002', + name: 'Data Compressed', + reference: 'https://attack.mitre.org/techniques/T1002', + tactics: 'exfiltration', + value: 'dataCompressed', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataDestructionDescription', + { defaultMessage: 'Data Destruction (T1485)' } + ), + id: 'T1485', + name: 'Data Destruction', + reference: 'https://attack.mitre.org/techniques/T1485', + tactics: 'impact', + value: 'dataDestruction', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncodingDescription', + { defaultMessage: 'Data Encoding (T1132)' } + ), + id: 'T1132', + name: 'Data Encoding', + reference: 'https://attack.mitre.org/techniques/T1132', + tactics: 'command-and-control', + value: 'dataEncoding', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedDescription', + { defaultMessage: 'Data Encrypted (T1022)' } + ), + id: 'T1022', + name: 'Data Encrypted', + reference: 'https://attack.mitre.org/techniques/T1022', + tactics: 'exfiltration', + value: 'dataEncrypted', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataEncryptedForImpactDescription', + { defaultMessage: 'Data Encrypted for Impact (T1486)' } + ), + id: 'T1486', + name: 'Data Encrypted for Impact', + reference: 'https://attack.mitre.org/techniques/T1486', + tactics: 'impact', + value: 'dataEncryptedForImpact', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataObfuscationDescription', + { defaultMessage: 'Data Obfuscation (T1001)' } + ), + id: 'T1001', + name: 'Data Obfuscation', + reference: 'https://attack.mitre.org/techniques/T1001', + tactics: 'command-and-control', + value: 'dataObfuscation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataStagedDescription', + { defaultMessage: 'Data Staged (T1074)' } + ), + id: 'T1074', + name: 'Data Staged', + reference: 'https://attack.mitre.org/techniques/T1074', + tactics: 'collection', + value: 'dataStaged', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataTransferSizeLimitsDescription', + { defaultMessage: 'Data Transfer Size Limits (T1030)' } + ), + id: 'T1030', + name: 'Data Transfer Size Limits', + reference: 'https://attack.mitre.org/techniques/T1030', + tactics: 'exfiltration', + value: 'dataTransferSizeLimits', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromCloudStorageObjectDescription', + { defaultMessage: 'Data from Cloud Storage Object (T1530)' } + ), + id: 'T1530', + name: 'Data from Cloud Storage Object', + reference: 'https://attack.mitre.org/techniques/T1530', + tactics: 'collection', + value: 'dataFromCloudStorageObject', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromInformationRepositoriesDescription', + { defaultMessage: 'Data from Information Repositories (T1213)' } + ), + id: 'T1213', + name: 'Data from Information Repositories', + reference: 'https://attack.mitre.org/techniques/T1213', + tactics: 'collection', + value: 'dataFromInformationRepositories', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromLocalSystemDescription', + { defaultMessage: 'Data from Local System (T1005)' } + ), + id: 'T1005', + name: 'Data from Local System', + reference: 'https://attack.mitre.org/techniques/T1005', + tactics: 'collection', + value: 'dataFromLocalSystem', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromNetworkSharedDriveDescription', + { defaultMessage: 'Data from Network Shared Drive (T1039)' } + ), + id: 'T1039', + name: 'Data from Network Shared Drive', + reference: 'https://attack.mitre.org/techniques/T1039', + tactics: 'collection', + value: 'dataFromNetworkSharedDrive', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dataFromRemovableMediaDescription', + { defaultMessage: 'Data from Removable Media (T1025)' } + ), + id: 'T1025', + name: 'Data from Removable Media', + reference: 'https://attack.mitre.org/techniques/T1025', + tactics: 'collection', + value: 'dataFromRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.defacementDescription', + { defaultMessage: 'Defacement (T1491)' } + ), + id: 'T1491', + name: 'Defacement', + reference: 'https://attack.mitre.org/techniques/T1491', + tactics: 'impact', + value: 'defacement', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.deobfuscateDecodeFilesOrInformationDescription', + { defaultMessage: 'Deobfuscate/Decode Files or Information (T1140)' } + ), + id: 'T1140', + name: 'Deobfuscate/Decode Files or Information', + reference: 'https://attack.mitre.org/techniques/T1140', + tactics: 'defense-evasion', + value: 'deobfuscateDecodeFilesOrInformation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.disablingSecurityToolsDescription', + { defaultMessage: 'Disabling Security Tools (T1089)' } + ), + id: 'T1089', + name: 'Disabling Security Tools', + reference: 'https://attack.mitre.org/techniques/T1089', + tactics: 'defense-evasion', + value: 'disablingSecurityTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.diskContentWipeDescription', + { defaultMessage: 'Disk Content Wipe (T1488)' } + ), + id: 'T1488', + name: 'Disk Content Wipe', + reference: 'https://attack.mitre.org/techniques/T1488', + tactics: 'impact', + value: 'diskContentWipe', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.diskStructureWipeDescription', + { defaultMessage: 'Disk Structure Wipe (T1487)' } + ), + id: 'T1487', + name: 'Disk Structure Wipe', + reference: 'https://attack.mitre.org/techniques/T1487', + tactics: 'impact', + value: 'diskStructureWipe', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainFrontingDescription', + { defaultMessage: 'Domain Fronting (T1172)' } + ), + id: 'T1172', + name: 'Domain Fronting', + reference: 'https://attack.mitre.org/techniques/T1172', + tactics: 'command-and-control', + value: 'domainFronting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainGenerationAlgorithmsDescription', + { defaultMessage: 'Domain Generation Algorithms (T1483)' } + ), + id: 'T1483', + name: 'Domain Generation Algorithms', + reference: 'https://attack.mitre.org/techniques/T1483', + tactics: 'command-and-control', + value: 'domainGenerationAlgorithms', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.domainTrustDiscoveryDescription', + { defaultMessage: 'Domain Trust Discovery (T1482)' } + ), + id: 'T1482', + name: 'Domain Trust Discovery', + reference: 'https://attack.mitre.org/techniques/T1482', + tactics: 'discovery', + value: 'domainTrustDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.driveByCompromiseDescription', + { defaultMessage: 'Drive-by Compromise (T1189)' } + ), + id: 'T1189', + name: 'Drive-by Compromise', + reference: 'https://attack.mitre.org/techniques/T1189', + tactics: 'initial-access', + value: 'driveByCompromise', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dylibHijackingDescription', + { defaultMessage: 'Dylib Hijacking (T1157)' } + ), + id: 'T1157', + name: 'Dylib Hijacking', + reference: 'https://attack.mitre.org/techniques/T1157', + tactics: 'persistence,privilege-escalation', + value: 'dylibHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.dynamicDataExchangeDescription', + { defaultMessage: 'Dynamic Data Exchange (T1173)' } + ), + id: 'T1173', + name: 'Dynamic Data Exchange', + reference: 'https://attack.mitre.org/techniques/T1173', + tactics: 'execution', + value: 'dynamicDataExchange', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.elevatedExecutionWithPromptDescription', + { defaultMessage: 'Elevated Execution with Prompt (T1514)' } + ), + id: 'T1514', + name: 'Elevated Execution with Prompt', + reference: 'https://attack.mitre.org/techniques/T1514', + tactics: 'privilege-escalation', + value: 'elevatedExecutionWithPrompt', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.emailCollectionDescription', + { defaultMessage: 'Email Collection (T1114)' } + ), + id: 'T1114', + name: 'Email Collection', + reference: 'https://attack.mitre.org/techniques/T1114', + tactics: 'collection', + value: 'emailCollection', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.emondDescription', { + defaultMessage: 'Emond (T1519)', + }), + id: 'T1519', + name: 'Emond', + reference: 'https://attack.mitre.org/techniques/T1519', + tactics: 'persistence,privilege-escalation', + value: 'emond', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.endpointDenialOfServiceDescription', + { defaultMessage: 'Endpoint Denial of Service (T1499)' } + ), + id: 'T1499', + name: 'Endpoint Denial of Service', + reference: 'https://attack.mitre.org/techniques/T1499', + tactics: 'impact', + value: 'endpointDenialOfService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionGuardrailsDescription', + { defaultMessage: 'Execution Guardrails (T1480)' } + ), + id: 'T1480', + name: 'Execution Guardrails', + reference: 'https://attack.mitre.org/techniques/T1480', + tactics: 'defense-evasion', + value: 'executionGuardrails', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughApiDescription', + { defaultMessage: 'Execution through API (T1106)' } + ), + id: 'T1106', + name: 'Execution through API', + reference: 'https://attack.mitre.org/techniques/T1106', + tactics: 'execution', + value: 'executionThroughApi', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.executionThroughModuleLoadDescription', + { defaultMessage: 'Execution through Module Load (T1129)' } + ), + id: 'T1129', + name: 'Execution through Module Load', + reference: 'https://attack.mitre.org/techniques/T1129', + tactics: 'execution', + value: 'executionThroughModuleLoad', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverAlternativeProtocolDescription', + { defaultMessage: 'Exfiltration Over Alternative Protocol (T1048)' } + ), + id: 'T1048', + name: 'Exfiltration Over Alternative Protocol', + reference: 'https://attack.mitre.org/techniques/T1048', + tactics: 'exfiltration', + value: 'exfiltrationOverAlternativeProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverCommandAndControlChannelDescription', + { defaultMessage: 'Exfiltration Over Command and Control Channel (T1041)' } + ), + id: 'T1041', + name: 'Exfiltration Over Command and Control Channel', + reference: 'https://attack.mitre.org/techniques/T1041', + tactics: 'exfiltration', + value: 'exfiltrationOverCommandAndControlChannel', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverOtherNetworkMediumDescription', + { defaultMessage: 'Exfiltration Over Other Network Medium (T1011)' } + ), + id: 'T1011', + name: 'Exfiltration Over Other Network Medium', + reference: 'https://attack.mitre.org/techniques/T1011', + tactics: 'exfiltration', + value: 'exfiltrationOverOtherNetworkMedium', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exfiltrationOverPhysicalMediumDescription', + { defaultMessage: 'Exfiltration Over Physical Medium (T1052)' } + ), + id: 'T1052', + name: 'Exfiltration Over Physical Medium', + reference: 'https://attack.mitre.org/techniques/T1052', + tactics: 'exfiltration', + value: 'exfiltrationOverPhysicalMedium', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitPublicFacingApplicationDescription', + { defaultMessage: 'Exploit Public-Facing Application (T1190)' } + ), + id: 'T1190', + name: 'Exploit Public-Facing Application', + reference: 'https://attack.mitre.org/techniques/T1190', + tactics: 'initial-access', + value: 'exploitPublicFacingApplication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForClientExecutionDescription', + { defaultMessage: 'Exploitation for Client Execution (T1203)' } + ), + id: 'T1203', + name: 'Exploitation for Client Execution', + reference: 'https://attack.mitre.org/techniques/T1203', + tactics: 'execution', + value: 'exploitationForClientExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForCredentialAccessDescription', + { defaultMessage: 'Exploitation for Credential Access (T1212)' } + ), + id: 'T1212', + name: 'Exploitation for Credential Access', + reference: 'https://attack.mitre.org/techniques/T1212', + tactics: 'credential-access', + value: 'exploitationForCredentialAccess', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForDefenseEvasionDescription', + { defaultMessage: 'Exploitation for Defense Evasion (T1211)' } + ), + id: 'T1211', + name: 'Exploitation for Defense Evasion', + reference: 'https://attack.mitre.org/techniques/T1211', + tactics: 'defense-evasion', + value: 'exploitationForDefenseEvasion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationForPrivilegeEscalationDescription', + { defaultMessage: 'Exploitation for Privilege Escalation (T1068)' } + ), + id: 'T1068', + name: 'Exploitation for Privilege Escalation', + reference: 'https://attack.mitre.org/techniques/T1068', + tactics: 'privilege-escalation', + value: 'exploitationForPrivilegeEscalation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.exploitationOfRemoteServicesDescription', + { defaultMessage: 'Exploitation of Remote Services (T1210)' } + ), + id: 'T1210', + name: 'Exploitation of Remote Services', + reference: 'https://attack.mitre.org/techniques/T1210', + tactics: 'lateral-movement', + value: 'exploitationOfRemoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.externalRemoteServicesDescription', + { defaultMessage: 'External Remote Services (T1133)' } + ), + id: 'T1133', + name: 'External Remote Services', + reference: 'https://attack.mitre.org/techniques/T1133', + tactics: 'persistence,initial-access', + value: 'externalRemoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.extraWindowMemoryInjectionDescription', + { defaultMessage: 'Extra Window Memory Injection (T1181)' } + ), + id: 'T1181', + name: 'Extra Window Memory Injection', + reference: 'https://attack.mitre.org/techniques/T1181', + tactics: 'defense-evasion,privilege-escalation', + value: 'extraWindowMemoryInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fallbackChannelsDescription', + { defaultMessage: 'Fallback Channels (T1008)' } + ), + id: 'T1008', + name: 'Fallback Channels', + reference: 'https://attack.mitre.org/techniques/T1008', + tactics: 'command-and-control', + value: 'fallbackChannels', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileDeletionDescription', + { defaultMessage: 'File Deletion (T1107)' } + ), + id: 'T1107', + name: 'File Deletion', + reference: 'https://attack.mitre.org/techniques/T1107', + tactics: 'defense-evasion', + value: 'fileDeletion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemLogicalOffsetsDescription', + { defaultMessage: 'File System Logical Offsets (T1006)' } + ), + id: 'T1006', + name: 'File System Logical Offsets', + reference: 'https://attack.mitre.org/techniques/T1006', + tactics: 'defense-evasion', + value: 'fileSystemLogicalOffsets', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileSystemPermissionsWeaknessDescription', + { defaultMessage: 'File System Permissions Weakness (T1044)' } + ), + id: 'T1044', + name: 'File System Permissions Weakness', + reference: 'https://attack.mitre.org/techniques/T1044', + tactics: 'persistence,privilege-escalation', + value: 'fileSystemPermissionsWeakness', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryDiscoveryDescription', + { defaultMessage: 'File and Directory Discovery (T1083)' } + ), + id: 'T1083', + name: 'File and Directory Discovery', + reference: 'https://attack.mitre.org/techniques/T1083', + tactics: 'discovery', + value: 'fileAndDirectoryDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.fileAndDirectoryPermissionsModificationDescription', + { defaultMessage: 'File and Directory Permissions Modification (T1222)' } + ), + id: 'T1222', + name: 'File and Directory Permissions Modification', + reference: 'https://attack.mitre.org/techniques/T1222', + tactics: 'defense-evasion', + value: 'fileAndDirectoryPermissionsModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.firmwareCorruptionDescription', + { defaultMessage: 'Firmware Corruption (T1495)' } + ), + id: 'T1495', + name: 'Firmware Corruption', + reference: 'https://attack.mitre.org/techniques/T1495', + tactics: 'impact', + value: 'firmwareCorruption', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.forcedAuthenticationDescription', + { defaultMessage: 'Forced Authentication (T1187)' } + ), + id: 'T1187', + name: 'Forced Authentication', + reference: 'https://attack.mitre.org/techniques/T1187', + tactics: 'credential-access', + value: 'forcedAuthentication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.gatekeeperBypassDescription', + { defaultMessage: 'Gatekeeper Bypass (T1144)' } + ), + id: 'T1144', + name: 'Gatekeeper Bypass', + reference: 'https://attack.mitre.org/techniques/T1144', + tactics: 'defense-evasion', + value: 'gatekeeperBypass', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription', + { defaultMessage: 'Graphical User Interface (T1061)' } + ), + id: 'T1061', + name: 'Graphical User Interface', + reference: 'https://attack.mitre.org/techniques/T1061', + tactics: 'execution', + value: 'graphicalUserInterface', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription', + { defaultMessage: 'Group Policy Modification (T1484)' } + ), + id: 'T1484', + name: 'Group Policy Modification', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: 'defense-evasion', + value: 'groupPolicyModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.histcontrolDescription', + { defaultMessage: 'HISTCONTROL (T1148)' } + ), + id: 'T1148', + name: 'HISTCONTROL', + reference: 'https://attack.mitre.org/techniques/T1148', + tactics: 'defense-evasion', + value: 'histcontrol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', + { defaultMessage: 'Hardware Additions (T1200)' } + ), + id: 'T1200', + name: 'Hardware Additions', + reference: 'https://attack.mitre.org/techniques/T1200', + tactics: 'initial-access', + value: 'hardwareAdditions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenFilesAndDirectoriesDescription', + { defaultMessage: 'Hidden Files and Directories (T1158)' } + ), + id: 'T1158', + name: 'Hidden Files and Directories', + reference: 'https://attack.mitre.org/techniques/T1158', + tactics: 'defense-evasion,persistence', + value: 'hiddenFilesAndDirectories', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenUsersDescription', + { defaultMessage: 'Hidden Users (T1147)' } + ), + id: 'T1147', + name: 'Hidden Users', + reference: 'https://attack.mitre.org/techniques/T1147', + tactics: 'defense-evasion', + value: 'hiddenUsers', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hiddenWindowDescription', + { defaultMessage: 'Hidden Window (T1143)' } + ), + id: 'T1143', + name: 'Hidden Window', + reference: 'https://attack.mitre.org/techniques/T1143', + tactics: 'defense-evasion', + value: 'hiddenWindow', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.hookingDescription', { + defaultMessage: 'Hooking (T1179)', + }), + id: 'T1179', + name: 'Hooking', + reference: 'https://attack.mitre.org/techniques/T1179', + tactics: 'persistence,privilege-escalation,credential-access', + value: 'hooking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.hypervisorDescription', + { defaultMessage: 'Hypervisor (T1062)' } + ), + id: 'T1062', + name: 'Hypervisor', + reference: 'https://attack.mitre.org/techniques/T1062', + tactics: 'persistence', + value: 'hypervisor', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.imageFileExecutionOptionsInjectionDescription', + { defaultMessage: 'Image File Execution Options Injection (T1183)' } + ), + id: 'T1183', + name: 'Image File Execution Options Injection', + reference: 'https://attack.mitre.org/techniques/T1183', + tactics: 'privilege-escalation,persistence,defense-evasion', + value: 'imageFileExecutionOptionsInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.implantContainerImageDescription', + { defaultMessage: 'Implant Container Image (T1525)' } + ), + id: 'T1525', + name: 'Implant Container Image', + reference: 'https://attack.mitre.org/techniques/T1525', + tactics: 'persistence', + value: 'implantContainerImage', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorBlockingDescription', + { defaultMessage: 'Indicator Blocking (T1054)' } + ), + id: 'T1054', + name: 'Indicator Blocking', + reference: 'https://attack.mitre.org/techniques/T1054', + tactics: 'defense-evasion', + value: 'indicatorBlocking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalFromToolsDescription', + { defaultMessage: 'Indicator Removal from Tools (T1066)' } + ), + id: 'T1066', + name: 'Indicator Removal from Tools', + reference: 'https://attack.mitre.org/techniques/T1066', + tactics: 'defense-evasion', + value: 'indicatorRemovalFromTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indicatorRemovalOnHostDescription', + { defaultMessage: 'Indicator Removal on Host (T1070)' } + ), + id: 'T1070', + name: 'Indicator Removal on Host', + reference: 'https://attack.mitre.org/techniques/T1070', + tactics: 'defense-evasion', + value: 'indicatorRemovalOnHost', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.indirectCommandExecutionDescription', + { defaultMessage: 'Indirect Command Execution (T1202)' } + ), + id: 'T1202', + name: 'Indirect Command Execution', + reference: 'https://attack.mitre.org/techniques/T1202', + tactics: 'defense-evasion', + value: 'indirectCommandExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inhibitSystemRecoveryDescription', + { defaultMessage: 'Inhibit System Recovery (T1490)' } + ), + id: 'T1490', + name: 'Inhibit System Recovery', + reference: 'https://attack.mitre.org/techniques/T1490', + tactics: 'impact', + value: 'inhibitSystemRecovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inputCaptureDescription', + { defaultMessage: 'Input Capture (T1056)' } + ), + id: 'T1056', + name: 'Input Capture', + reference: 'https://attack.mitre.org/techniques/T1056', + tactics: 'collection,credential-access', + value: 'inputCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.inputPromptDescription', + { defaultMessage: 'Input Prompt (T1141)' } + ), + id: 'T1141', + name: 'Input Prompt', + reference: 'https://attack.mitre.org/techniques/T1141', + tactics: 'credential-access', + value: 'inputPrompt', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.installRootCertificateDescription', + { defaultMessage: 'Install Root Certificate (T1130)' } + ), + id: 'T1130', + name: 'Install Root Certificate', + reference: 'https://attack.mitre.org/techniques/T1130', + tactics: 'defense-evasion', + value: 'installRootCertificate', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.installUtilDescription', + { defaultMessage: 'InstallUtil (T1118)' } + ), + id: 'T1118', + name: 'InstallUtil', + reference: 'https://attack.mitre.org/techniques/T1118', + tactics: 'defense-evasion,execution', + value: 'installUtil', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.internalSpearphishingDescription', + { defaultMessage: 'Internal Spearphishing (T1534)' } + ), + id: 'T1534', + name: 'Internal Spearphishing', + reference: 'https://attack.mitre.org/techniques/T1534', + tactics: 'lateral-movement', + value: 'internalSpearphishing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.kerberoastingDescription', + { defaultMessage: 'Kerberoasting (T1208)' } + ), + id: 'T1208', + name: 'Kerberoasting', + reference: 'https://attack.mitre.org/techniques/T1208', + tactics: 'credential-access', + value: 'kerberoasting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.kernelModulesAndExtensionsDescription', + { defaultMessage: 'Kernel Modules and Extensions (T1215)' } + ), + id: 'T1215', + name: 'Kernel Modules and Extensions', + reference: 'https://attack.mitre.org/techniques/T1215', + tactics: 'persistence', + value: 'kernelModulesAndExtensions', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.keychainDescription', { + defaultMessage: 'Keychain (T1142)', + }), + id: 'T1142', + name: 'Keychain', + reference: 'https://attack.mitre.org/techniques/T1142', + tactics: 'credential-access', + value: 'keychain', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lcLoadDylibAdditionDescription', + { defaultMessage: 'LC_LOAD_DYLIB Addition (T1161)' } + ), + id: 'T1161', + name: 'LC_LOAD_DYLIB Addition', + reference: 'https://attack.mitre.org/techniques/T1161', + tactics: 'persistence', + value: 'lcLoadDylibAddition', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lcMainHijackingDescription', + { defaultMessage: 'LC_MAIN Hijacking (T1149)' } + ), + id: 'T1149', + name: 'LC_MAIN Hijacking', + reference: 'https://attack.mitre.org/techniques/T1149', + tactics: 'defense-evasion', + value: 'lcMainHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.llmnrNbtNsPoisoningAndRelayDescription', + { defaultMessage: 'LLMNR/NBT-NS Poisoning and Relay (T1171)' } + ), + id: 'T1171', + name: 'LLMNR/NBT-NS Poisoning and Relay', + reference: 'https://attack.mitre.org/techniques/T1171', + tactics: 'credential-access', + value: 'llmnrNbtNsPoisoningAndRelay', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.lsassDriverDescription', + { defaultMessage: 'LSASS Driver (T1177)' } + ), + id: 'T1177', + name: 'LSASS Driver', + reference: 'https://attack.mitre.org/techniques/T1177', + tactics: 'execution,persistence', + value: 'lsassDriver', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.launchAgentDescription', + { defaultMessage: 'Launch Agent (T1159)' } + ), + id: 'T1159', + name: 'Launch Agent', + reference: 'https://attack.mitre.org/techniques/T1159', + tactics: 'persistence', + value: 'launchAgent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.launchDaemonDescription', + { defaultMessage: 'Launch Daemon (T1160)' } + ), + id: 'T1160', + name: 'Launch Daemon', + reference: 'https://attack.mitre.org/techniques/T1160', + tactics: 'persistence,privilege-escalation', + value: 'launchDaemon', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.launchctlDescription', { + defaultMessage: 'Launchctl (T1152)', + }), + id: 'T1152', + name: 'Launchctl', + reference: 'https://attack.mitre.org/techniques/T1152', + tactics: 'defense-evasion,execution,persistence', + value: 'launchctl', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.localJobSchedulingDescription', + { defaultMessage: 'Local Job Scheduling (T1168)' } + ), + id: 'T1168', + name: 'Local Job Scheduling', + reference: 'https://attack.mitre.org/techniques/T1168', + tactics: 'persistence,execution', + value: 'localJobScheduling', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.loginItemDescription', { + defaultMessage: 'Login Item (T1162)', + }), + id: 'T1162', + name: 'Login Item', + reference: 'https://attack.mitre.org/techniques/T1162', + tactics: 'persistence', + value: 'loginItem', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.logonScriptsDescription', + { defaultMessage: 'Logon Scripts (T1037)' } + ), + id: 'T1037', + name: 'Logon Scripts', + reference: 'https://attack.mitre.org/techniques/T1037', + tactics: 'lateral-movement,persistence', + value: 'logonScripts', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.manInTheBrowserDescription', + { defaultMessage: 'Man in the Browser (T1185)' } + ), + id: 'T1185', + name: 'Man in the Browser', + reference: 'https://attack.mitre.org/techniques/T1185', + tactics: 'collection', + value: 'manInTheBrowser', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.masqueradingDescription', + { defaultMessage: 'Masquerading (T1036)' } + ), + id: 'T1036', + name: 'Masquerading', + reference: 'https://attack.mitre.org/techniques/T1036', + tactics: 'defense-evasion', + value: 'masquerading', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyExistingServiceDescription', + { defaultMessage: 'Modify Existing Service (T1031)' } + ), + id: 'T1031', + name: 'Modify Existing Service', + reference: 'https://attack.mitre.org/techniques/T1031', + tactics: 'persistence', + value: 'modifyExistingService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.modifyRegistryDescription', + { defaultMessage: 'Modify Registry (T1112)' } + ), + id: 'T1112', + name: 'Modify Registry', + reference: 'https://attack.mitre.org/techniques/T1112', + tactics: 'defense-evasion', + value: 'modifyRegistry', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.mshtaDescription', { + defaultMessage: 'Mshta (T1170)', + }), + id: 'T1170', + name: 'Mshta', + reference: 'https://attack.mitre.org/techniques/T1170', + tactics: 'defense-evasion,execution', + value: 'mshta', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multiStageChannelsDescription', + { defaultMessage: 'Multi-Stage Channels (T1104)' } + ), + id: 'T1104', + name: 'Multi-Stage Channels', + reference: 'https://attack.mitre.org/techniques/T1104', + tactics: 'command-and-control', + value: 'multiStageChannels', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multiHopProxyDescription', + { defaultMessage: 'Multi-hop Proxy (T1188)' } + ), + id: 'T1188', + name: 'Multi-hop Proxy', + reference: 'https://attack.mitre.org/techniques/T1188', + tactics: 'command-and-control', + value: 'multiHopProxy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multibandCommunicationDescription', + { defaultMessage: 'Multiband Communication (T1026)' } + ), + id: 'T1026', + name: 'Multiband Communication', + reference: 'https://attack.mitre.org/techniques/T1026', + tactics: 'command-and-control', + value: 'multibandCommunication', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.multilayerEncryptionDescription', + { defaultMessage: 'Multilayer Encryption (T1079)' } + ), + id: 'T1079', + name: 'Multilayer Encryption', + reference: 'https://attack.mitre.org/techniques/T1079', + tactics: 'command-and-control', + value: 'multilayerEncryption', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.ntfsFileAttributesDescription', + { defaultMessage: 'NTFS File Attributes (T1096)' } + ), + id: 'T1096', + name: 'NTFS File Attributes', + reference: 'https://attack.mitre.org/techniques/T1096', + tactics: 'defense-evasion', + value: 'ntfsFileAttributes', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.netshHelperDllDescription', + { defaultMessage: 'Netsh Helper DLL (T1128)' } + ), + id: 'T1128', + name: 'Netsh Helper DLL', + reference: 'https://attack.mitre.org/techniques/T1128', + tactics: 'persistence', + value: 'netshHelperDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkDenialOfServiceDescription', + { defaultMessage: 'Network Denial of Service (T1498)' } + ), + id: 'T1498', + name: 'Network Denial of Service', + reference: 'https://attack.mitre.org/techniques/T1498', + tactics: 'impact', + value: 'networkDenialOfService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkServiceScanningDescription', + { defaultMessage: 'Network Service Scanning (T1046)' } + ), + id: 'T1046', + name: 'Network Service Scanning', + reference: 'https://attack.mitre.org/techniques/T1046', + tactics: 'discovery', + value: 'networkServiceScanning', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareConnectionRemovalDescription', + { defaultMessage: 'Network Share Connection Removal (T1126)' } + ), + id: 'T1126', + name: 'Network Share Connection Removal', + reference: 'https://attack.mitre.org/techniques/T1126', + tactics: 'defense-evasion', + value: 'networkShareConnectionRemoval', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkShareDiscoveryDescription', + { defaultMessage: 'Network Share Discovery (T1135)' } + ), + id: 'T1135', + name: 'Network Share Discovery', + reference: 'https://attack.mitre.org/techniques/T1135', + tactics: 'discovery', + value: 'networkShareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.networkSniffingDescription', + { defaultMessage: 'Network Sniffing (T1040)' } + ), + id: 'T1040', + name: 'Network Sniffing', + reference: 'https://attack.mitre.org/techniques/T1040', + tactics: 'credential-access,discovery', + value: 'networkSniffing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.newServiceDescription', + { defaultMessage: 'New Service (T1050)' } + ), + id: 'T1050', + name: 'New Service', + reference: 'https://attack.mitre.org/techniques/T1050', + tactics: 'persistence,privilege-escalation', + value: 'newService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.obfuscatedFilesOrInformationDescription', + { defaultMessage: 'Obfuscated Files or Information (T1027)' } + ), + id: 'T1027', + name: 'Obfuscated Files or Information', + reference: 'https://attack.mitre.org/techniques/T1027', + tactics: 'defense-evasion', + value: 'obfuscatedFilesOrInformation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.officeApplicationStartupDescription', + { defaultMessage: 'Office Application Startup (T1137)' } + ), + id: 'T1137', + name: 'Office Application Startup', + reference: 'https://attack.mitre.org/techniques/T1137', + tactics: 'persistence', + value: 'officeApplicationStartup', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.parentPidSpoofingDescription', + { defaultMessage: 'Parent PID Spoofing (T1502)' } + ), + id: 'T1502', + name: 'Parent PID Spoofing', + reference: 'https://attack.mitre.org/techniques/T1502', + tactics: 'defense-evasion,privilege-escalation', + value: 'parentPidSpoofing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheHashDescription', + { defaultMessage: 'Pass the Hash (T1075)' } + ), + id: 'T1075', + name: 'Pass the Hash', + reference: 'https://attack.mitre.org/techniques/T1075', + tactics: 'lateral-movement', + value: 'passTheHash', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passTheTicketDescription', + { defaultMessage: 'Pass the Ticket (T1097)' } + ), + id: 'T1097', + name: 'Pass the Ticket', + reference: 'https://attack.mitre.org/techniques/T1097', + tactics: 'lateral-movement', + value: 'passTheTicket', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordFilterDllDescription', + { defaultMessage: 'Password Filter DLL (T1174)' } + ), + id: 'T1174', + name: 'Password Filter DLL', + reference: 'https://attack.mitre.org/techniques/T1174', + tactics: 'credential-access', + value: 'passwordFilterDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.passwordPolicyDiscoveryDescription', + { defaultMessage: 'Password Policy Discovery (T1201)' } + ), + id: 'T1201', + name: 'Password Policy Discovery', + reference: 'https://attack.mitre.org/techniques/T1201', + tactics: 'discovery', + value: 'passwordPolicyDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.pathInterceptionDescription', + { defaultMessage: 'Path Interception (T1034)' } + ), + id: 'T1034', + name: 'Path Interception', + reference: 'https://attack.mitre.org/techniques/T1034', + tactics: 'persistence,privilege-escalation', + value: 'pathInterception', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.peripheralDeviceDiscoveryDescription', + { defaultMessage: 'Peripheral Device Discovery (T1120)' } + ), + id: 'T1120', + name: 'Peripheral Device Discovery', + reference: 'https://attack.mitre.org/techniques/T1120', + tactics: 'discovery', + value: 'peripheralDeviceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.permissionGroupsDiscoveryDescription', + { defaultMessage: 'Permission Groups Discovery (T1069)' } + ), + id: 'T1069', + name: 'Permission Groups Discovery', + reference: 'https://attack.mitre.org/techniques/T1069', + tactics: 'discovery', + value: 'permissionGroupsDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.plistModificationDescription', + { defaultMessage: 'Plist Modification (T1150)' } + ), + id: 'T1150', + name: 'Plist Modification', + reference: 'https://attack.mitre.org/techniques/T1150', + tactics: 'defense-evasion,persistence,privilege-escalation', + value: 'plistModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.portKnockingDescription', + { defaultMessage: 'Port Knocking (T1205)' } + ), + id: 'T1205', + name: 'Port Knocking', + reference: 'https://attack.mitre.org/techniques/T1205', + tactics: 'defense-evasion,persistence,command-and-control', + value: 'portKnocking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.portMonitorsDescription', + { defaultMessage: 'Port Monitors (T1013)' } + ), + id: 'T1013', + name: 'Port Monitors', + reference: 'https://attack.mitre.org/techniques/T1013', + tactics: 'persistence,privilege-escalation', + value: 'portMonitors', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellDescription', + { defaultMessage: 'PowerShell (T1086)' } + ), + id: 'T1086', + name: 'PowerShell', + reference: 'https://attack.mitre.org/techniques/T1086', + tactics: 'execution', + value: 'powerShell', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.powerShellProfileDescription', + { defaultMessage: 'PowerShell Profile (T1504)' } + ), + id: 'T1504', + name: 'PowerShell Profile', + reference: 'https://attack.mitre.org/techniques/T1504', + tactics: 'persistence,privilege-escalation', + value: 'powerShellProfile', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.privateKeysDescription', + { defaultMessage: 'Private Keys (T1145)' } + ), + id: 'T1145', + name: 'Private Keys', + reference: 'https://attack.mitre.org/techniques/T1145', + tactics: 'credential-access', + value: 'privateKeys', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processDiscoveryDescription', + { defaultMessage: 'Process Discovery (T1057)' } + ), + id: 'T1057', + name: 'Process Discovery', + reference: 'https://attack.mitre.org/techniques/T1057', + tactics: 'discovery', + value: 'processDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processDoppelgangingDescription', + { defaultMessage: 'Process Doppelgänging (T1186)' } + ), + id: 'T1186', + name: 'Process Doppelgänging', + reference: 'https://attack.mitre.org/techniques/T1186', + tactics: 'defense-evasion', + value: 'processDoppelganging', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processHollowingDescription', + { defaultMessage: 'Process Hollowing (T1093)' } + ), + id: 'T1093', + name: 'Process Hollowing', + reference: 'https://attack.mitre.org/techniques/T1093', + tactics: 'defense-evasion', + value: 'processHollowing', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.processInjectionDescription', + { defaultMessage: 'Process Injection (T1055)' } + ), + id: 'T1055', + name: 'Process Injection', + reference: 'https://attack.mitre.org/techniques/T1055', + tactics: 'defense-evasion,privilege-escalation', + value: 'processInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.queryRegistryDescription', + { defaultMessage: 'Query Registry (T1012)' } + ), + id: 'T1012', + name: 'Query Registry', + reference: 'https://attack.mitre.org/techniques/T1012', + tactics: 'discovery', + value: 'queryRegistry', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rcCommonDescription', { + defaultMessage: 'Rc.common (T1163)', + }), + id: 'T1163', + name: 'Rc.common', + reference: 'https://attack.mitre.org/techniques/T1163', + tactics: 'persistence', + value: 'rcCommon', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.reOpenedApplicationsDescription', + { defaultMessage: 'Re-opened Applications (T1164)' } + ), + id: 'T1164', + name: 'Re-opened Applications', + reference: 'https://attack.mitre.org/techniques/T1164', + tactics: 'persistence', + value: 'reOpenedApplications', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.redundantAccessDescription', + { defaultMessage: 'Redundant Access (T1108)' } + ), + id: 'T1108', + name: 'Redundant Access', + reference: 'https://attack.mitre.org/techniques/T1108', + tactics: 'defense-evasion,persistence', + value: 'redundantAccess', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.registryRunKeysStartupFolderDescription', + { defaultMessage: 'Registry Run Keys / Startup Folder (T1060)' } + ), + id: 'T1060', + name: 'Registry Run Keys / Startup Folder', + reference: 'https://attack.mitre.org/techniques/T1060', + tactics: 'persistence', + value: 'registryRunKeysStartupFolder', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.regsvcsRegasmDescription', + { defaultMessage: 'Regsvcs/Regasm (T1121)' } + ), + id: 'T1121', + name: 'Regsvcs/Regasm', + reference: 'https://attack.mitre.org/techniques/T1121', + tactics: 'defense-evasion,execution', + value: 'regsvcsRegasm', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.regsvr32Description', { + defaultMessage: 'Regsvr32 (T1117)', + }), + id: 'T1117', + name: 'Regsvr32', + reference: 'https://attack.mitre.org/techniques/T1117', + tactics: 'defense-evasion,execution', + value: 'regsvr32', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteAccessToolsDescription', + { defaultMessage: 'Remote Access Tools (T1219)' } + ), + id: 'T1219', + name: 'Remote Access Tools', + reference: 'https://attack.mitre.org/techniques/T1219', + tactics: 'command-and-control', + value: 'remoteAccessTools', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteDesktopProtocolDescription', + { defaultMessage: 'Remote Desktop Protocol (T1076)' } + ), + id: 'T1076', + name: 'Remote Desktop Protocol', + reference: 'https://attack.mitre.org/techniques/T1076', + tactics: 'lateral-movement', + value: 'remoteDesktopProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteFileCopyDescription', + { defaultMessage: 'Remote File Copy (T1105)' } + ), + id: 'T1105', + name: 'Remote File Copy', + reference: 'https://attack.mitre.org/techniques/T1105', + tactics: 'command-and-control,lateral-movement', + value: 'remoteFileCopy', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteServicesDescription', + { defaultMessage: 'Remote Services (T1021)' } + ), + id: 'T1021', + name: 'Remote Services', + reference: 'https://attack.mitre.org/techniques/T1021', + tactics: 'lateral-movement', + value: 'remoteServices', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.remoteSystemDiscoveryDescription', + { defaultMessage: 'Remote System Discovery (T1018)' } + ), + id: 'T1018', + name: 'Remote System Discovery', + reference: 'https://attack.mitre.org/techniques/T1018', + tactics: 'discovery', + value: 'remoteSystemDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.replicationThroughRemovableMediaDescription', + { defaultMessage: 'Replication Through Removable Media (T1091)' } + ), + id: 'T1091', + name: 'Replication Through Removable Media', + reference: 'https://attack.mitre.org/techniques/T1091', + tactics: 'lateral-movement,initial-access', + value: 'replicationThroughRemovableMedia', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.resourceHijackingDescription', + { defaultMessage: 'Resource Hijacking (T1496)' } + ), + id: 'T1496', + name: 'Resource Hijacking', + reference: 'https://attack.mitre.org/techniques/T1496', + tactics: 'impact', + value: 'resourceHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.revertCloudInstanceDescription', + { defaultMessage: 'Revert Cloud Instance (T1536)' } + ), + id: 'T1536', + name: 'Revert Cloud Instance', + reference: 'https://attack.mitre.org/techniques/T1536', + tactics: 'defense-evasion', + value: 'revertCloudInstance', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rootkitDescription', { + defaultMessage: 'Rootkit (T1014)', + }), + id: 'T1014', + name: 'Rootkit', + reference: 'https://attack.mitre.org/techniques/T1014', + tactics: 'defense-evasion', + value: 'rootkit', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.rundll32Description', { + defaultMessage: 'Rundll32 (T1085)', + }), + id: 'T1085', + name: 'Rundll32', + reference: 'https://attack.mitre.org/techniques/T1085', + tactics: 'defense-evasion,execution', + value: 'rundll32', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.runtimeDataManipulationDescription', + { defaultMessage: 'Runtime Data Manipulation (T1494)' } + ), + id: 'T1494', + name: 'Runtime Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1494', + tactics: 'impact', + value: 'runtimeDataManipulation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sidHistoryInjectionDescription', + { defaultMessage: 'SID-History Injection (T1178)' } + ), + id: 'T1178', + name: 'SID-History Injection', + reference: 'https://attack.mitre.org/techniques/T1178', + tactics: 'privilege-escalation', + value: 'sidHistoryInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sipAndTrustProviderHijackingDescription', + { defaultMessage: 'SIP and Trust Provider Hijacking (T1198)' } + ), + id: 'T1198', + name: 'SIP and Trust Provider Hijacking', + reference: 'https://attack.mitre.org/techniques/T1198', + tactics: 'defense-evasion,persistence', + value: 'sipAndTrustProviderHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sshHijackingDescription', + { defaultMessage: 'SSH Hijacking (T1184)' } + ), + id: 'T1184', + name: 'SSH Hijacking', + reference: 'https://attack.mitre.org/techniques/T1184', + tactics: 'lateral-movement', + value: 'sshHijacking', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTaskDescription', + { defaultMessage: 'Scheduled Task (T1053)' } + ), + id: 'T1053', + name: 'Scheduled Task', + reference: 'https://attack.mitre.org/techniques/T1053', + tactics: 'execution,persistence,privilege-escalation', + value: 'scheduledTask', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.scheduledTransferDescription', + { defaultMessage: 'Scheduled Transfer (T1029)' } + ), + id: 'T1029', + name: 'Scheduled Transfer', + reference: 'https://attack.mitre.org/techniques/T1029', + tactics: 'exfiltration', + value: 'scheduledTransfer', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.screenCaptureDescription', + { defaultMessage: 'Screen Capture (T1113)' } + ), + id: 'T1113', + name: 'Screen Capture', + reference: 'https://attack.mitre.org/techniques/T1113', + tactics: 'collection', + value: 'screenCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.screensaverDescription', + { defaultMessage: 'Screensaver (T1180)' } + ), + id: 'T1180', + name: 'Screensaver', + reference: 'https://attack.mitre.org/techniques/T1180', + tactics: 'persistence', + value: 'screensaver', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.scriptingDescription', { + defaultMessage: 'Scripting (T1064)', + }), + id: 'T1064', + name: 'Scripting', + reference: 'https://attack.mitre.org/techniques/T1064', + tactics: 'defense-evasion,execution', + value: 'scripting', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySoftwareDiscoveryDescription', + { defaultMessage: 'Security Software Discovery (T1063)' } + ), + id: 'T1063', + name: 'Security Software Discovery', + reference: 'https://attack.mitre.org/techniques/T1063', + tactics: 'discovery', + value: 'securitySoftwareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitySupportProviderDescription', + { defaultMessage: 'Security Support Provider (T1101)' } + ), + id: 'T1101', + name: 'Security Support Provider', + reference: 'https://attack.mitre.org/techniques/T1101', + tactics: 'persistence', + value: 'securitySupportProvider', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.securitydMemoryDescription', + { defaultMessage: 'Securityd Memory (T1167)' } + ), + id: 'T1167', + name: 'Securityd Memory', + reference: 'https://attack.mitre.org/techniques/T1167', + tactics: 'credential-access', + value: 'securitydMemory', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serverSoftwareComponentDescription', + { defaultMessage: 'Server Software Component (T1505)' } + ), + id: 'T1505', + name: 'Server Software Component', + reference: 'https://attack.mitre.org/techniques/T1505', + tactics: 'persistence', + value: 'serverSoftwareComponent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceExecutionDescription', + { defaultMessage: 'Service Execution (T1035)' } + ), + id: 'T1035', + name: 'Service Execution', + reference: 'https://attack.mitre.org/techniques/T1035', + tactics: 'execution', + value: 'serviceExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceRegistryPermissionsWeaknessDescription', + { defaultMessage: 'Service Registry Permissions Weakness (T1058)' } + ), + id: 'T1058', + name: 'Service Registry Permissions Weakness', + reference: 'https://attack.mitre.org/techniques/T1058', + tactics: 'persistence,privilege-escalation', + value: 'serviceRegistryPermissionsWeakness', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.serviceStopDescription', + { defaultMessage: 'Service Stop (T1489)' } + ), + id: 'T1489', + name: 'Service Stop', + reference: 'https://attack.mitre.org/techniques/T1489', + tactics: 'impact', + value: 'serviceStop', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.setuidAndSetgidDescription', + { defaultMessage: 'Setuid and Setgid (T1166)' } + ), + id: 'T1166', + name: 'Setuid and Setgid', + reference: 'https://attack.mitre.org/techniques/T1166', + tactics: 'privilege-escalation,persistence', + value: 'setuidAndSetgid', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sharedWebrootDescription', + { defaultMessage: 'Shared Webroot (T1051)' } + ), + id: 'T1051', + name: 'Shared Webroot', + reference: 'https://attack.mitre.org/techniques/T1051', + tactics: 'lateral-movement', + value: 'sharedWebroot', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.shortcutModificationDescription', + { defaultMessage: 'Shortcut Modification (T1023)' } + ), + id: 'T1023', + name: 'Shortcut Modification', + reference: 'https://attack.mitre.org/techniques/T1023', + tactics: 'persistence', + value: 'shortcutModification', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.signedBinaryProxyExecutionDescription', + { defaultMessage: 'Signed Binary Proxy Execution (T1218)' } + ), + id: 'T1218', + name: 'Signed Binary Proxy Execution', + reference: 'https://attack.mitre.org/techniques/T1218', + tactics: 'defense-evasion,execution', + value: 'signedBinaryProxyExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.signedScriptProxyExecutionDescription', + { defaultMessage: 'Signed Script Proxy Execution (T1216)' } + ), + id: 'T1216', + name: 'Signed Script Proxy Execution', + reference: 'https://attack.mitre.org/techniques/T1216', + tactics: 'defense-evasion,execution', + value: 'signedScriptProxyExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.softwareDiscoveryDescription', + { defaultMessage: 'Software Discovery (T1518)' } + ), + id: 'T1518', + name: 'Software Discovery', + reference: 'https://attack.mitre.org/techniques/T1518', + tactics: 'discovery', + value: 'softwareDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.softwarePackingDescription', + { defaultMessage: 'Software Packing (T1045)' } + ), + id: 'T1045', + name: 'Software Packing', + reference: 'https://attack.mitre.org/techniques/T1045', + tactics: 'defense-evasion', + value: 'softwarePacking', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sourceDescription', { + defaultMessage: 'Source (T1153)', + }), + id: 'T1153', + name: 'Source', + reference: 'https://attack.mitre.org/techniques/T1153', + tactics: 'execution', + value: 'source', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spaceAfterFilenameDescription', + { defaultMessage: 'Space after Filename (T1151)' } + ), + id: 'T1151', + name: 'Space after Filename', + reference: 'https://attack.mitre.org/techniques/T1151', + tactics: 'defense-evasion,execution', + value: 'spaceAfterFilename', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingAttachmentDescription', + { defaultMessage: 'Spearphishing Attachment (T1193)' } + ), + id: 'T1193', + name: 'Spearphishing Attachment', + reference: 'https://attack.mitre.org/techniques/T1193', + tactics: 'initial-access', + value: 'spearphishingAttachment', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingLinkDescription', + { defaultMessage: 'Spearphishing Link (T1192)' } + ), + id: 'T1192', + name: 'Spearphishing Link', + reference: 'https://attack.mitre.org/techniques/T1192', + tactics: 'initial-access', + value: 'spearphishingLink', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.spearphishingViaServiceDescription', + { defaultMessage: 'Spearphishing via Service (T1194)' } + ), + id: 'T1194', + name: 'Spearphishing via Service', + reference: 'https://attack.mitre.org/techniques/T1194', + tactics: 'initial-access', + value: 'spearphishingViaService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardApplicationLayerProtocolDescription', + { defaultMessage: 'Standard Application Layer Protocol (T1071)' } + ), + id: 'T1071', + name: 'Standard Application Layer Protocol', + reference: 'https://attack.mitre.org/techniques/T1071', + tactics: 'command-and-control', + value: 'standardApplicationLayerProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardCryptographicProtocolDescription', + { defaultMessage: 'Standard Cryptographic Protocol (T1032)' } + ), + id: 'T1032', + name: 'Standard Cryptographic Protocol', + reference: 'https://attack.mitre.org/techniques/T1032', + tactics: 'command-and-control', + value: 'standardCryptographicProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.standardNonApplicationLayerProtocolDescription', + { defaultMessage: 'Standard Non-Application Layer Protocol (T1095)' } + ), + id: 'T1095', + name: 'Standard Non-Application Layer Protocol', + reference: 'https://attack.mitre.org/techniques/T1095', + tactics: 'command-and-control', + value: 'standardNonApplicationLayerProtocol', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.startupItemsDescription', + { defaultMessage: 'Startup Items (T1165)' } + ), + id: 'T1165', + name: 'Startup Items', + reference: 'https://attack.mitre.org/techniques/T1165', + tactics: 'persistence,privilege-escalation', + value: 'startupItems', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.stealApplicationAccessTokenDescription', + { defaultMessage: 'Steal Application Access Token (T1528)' } + ), + id: 'T1528', + name: 'Steal Application Access Token', + reference: 'https://attack.mitre.org/techniques/T1528', + tactics: 'credential-access', + value: 'stealApplicationAccessToken', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.stealWebSessionCookieDescription', + { defaultMessage: 'Steal Web Session Cookie (T1539)' } + ), + id: 'T1539', + name: 'Steal Web Session Cookie', + reference: 'https://attack.mitre.org/techniques/T1539', + tactics: 'credential-access', + value: 'stealWebSessionCookie', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.storedDataManipulationDescription', + { defaultMessage: 'Stored Data Manipulation (T1492)' } + ), + id: 'T1492', + name: 'Stored Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1492', + tactics: 'impact', + value: 'storedDataManipulation', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.sudoDescription', { + defaultMessage: 'Sudo (T1169)', + }), + id: 'T1169', + name: 'Sudo', + reference: 'https://attack.mitre.org/techniques/T1169', + tactics: 'privilege-escalation', + value: 'sudo', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.sudoCachingDescription', + { defaultMessage: 'Sudo Caching (T1206)' } + ), + id: 'T1206', + name: 'Sudo Caching', + reference: 'https://attack.mitre.org/techniques/T1206', + tactics: 'privilege-escalation', + value: 'sudoCaching', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.supplyChainCompromiseDescription', + { defaultMessage: 'Supply Chain Compromise (T1195)' } + ), + id: 'T1195', + name: 'Supply Chain Compromise', + reference: 'https://attack.mitre.org/techniques/T1195', + tactics: 'initial-access', + value: 'supplyChainCompromise', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemFirmwareDescription', + { defaultMessage: 'System Firmware (T1019)' } + ), + id: 'T1019', + name: 'System Firmware', + reference: 'https://attack.mitre.org/techniques/T1019', + tactics: 'persistence', + value: 'systemFirmware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemInformationDiscoveryDescription', + { defaultMessage: 'System Information Discovery (T1082)' } + ), + id: 'T1082', + name: 'System Information Discovery', + reference: 'https://attack.mitre.org/techniques/T1082', + tactics: 'discovery', + value: 'systemInformationDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConfigurationDiscoveryDescription', + { defaultMessage: 'System Network Configuration Discovery (T1016)' } + ), + id: 'T1016', + name: 'System Network Configuration Discovery', + reference: 'https://attack.mitre.org/techniques/T1016', + tactics: 'discovery', + value: 'systemNetworkConfigurationDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemNetworkConnectionsDiscoveryDescription', + { defaultMessage: 'System Network Connections Discovery (T1049)' } + ), + id: 'T1049', + name: 'System Network Connections Discovery', + reference: 'https://attack.mitre.org/techniques/T1049', + tactics: 'discovery', + value: 'systemNetworkConnectionsDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemOwnerUserDiscoveryDescription', + { defaultMessage: 'System Owner/User Discovery (T1033)' } + ), + id: 'T1033', + name: 'System Owner/User Discovery', + reference: 'https://attack.mitre.org/techniques/T1033', + tactics: 'discovery', + value: 'systemOwnerUserDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemServiceDiscoveryDescription', + { defaultMessage: 'System Service Discovery (T1007)' } + ), + id: 'T1007', + name: 'System Service Discovery', + reference: 'https://attack.mitre.org/techniques/T1007', + tactics: 'discovery', + value: 'systemServiceDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemShutdownRebootDescription', + { defaultMessage: 'System Shutdown/Reboot (T1529)' } + ), + id: 'T1529', + name: 'System Shutdown/Reboot', + reference: 'https://attack.mitre.org/techniques/T1529', + tactics: 'impact', + value: 'systemShutdownReboot', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemTimeDiscoveryDescription', + { defaultMessage: 'System Time Discovery (T1124)' } + ), + id: 'T1124', + name: 'System Time Discovery', + reference: 'https://attack.mitre.org/techniques/T1124', + tactics: 'discovery', + value: 'systemTimeDiscovery', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.systemdServiceDescription', + { defaultMessage: 'Systemd Service (T1501)' } + ), + id: 'T1501', + name: 'Systemd Service', + reference: 'https://attack.mitre.org/techniques/T1501', + tactics: 'persistence', + value: 'systemdService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.taintSharedContentDescription', + { defaultMessage: 'Taint Shared Content (T1080)' } + ), + id: 'T1080', + name: 'Taint Shared Content', + reference: 'https://attack.mitre.org/techniques/T1080', + tactics: 'lateral-movement', + value: 'taintSharedContent', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.templateInjectionDescription', + { defaultMessage: 'Template Injection (T1221)' } + ), + id: 'T1221', + name: 'Template Injection', + reference: 'https://attack.mitre.org/techniques/T1221', + tactics: 'defense-evasion', + value: 'templateInjection', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.thirdPartySoftwareDescription', + { defaultMessage: 'Third-party Software (T1072)' } + ), + id: 'T1072', + name: 'Third-party Software', + reference: 'https://attack.mitre.org/techniques/T1072', + tactics: 'execution,lateral-movement', + value: 'thirdPartySoftware', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.timeProvidersDescription', + { defaultMessage: 'Time Providers (T1209)' } + ), + id: 'T1209', + name: 'Time Providers', + reference: 'https://attack.mitre.org/techniques/T1209', + tactics: 'persistence', + value: 'timeProviders', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.timestompDescription', { + defaultMessage: 'Timestomp (T1099)', + }), + id: 'T1099', + name: 'Timestomp', + reference: 'https://attack.mitre.org/techniques/T1099', + tactics: 'defense-evasion', + value: 'timestomp', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.transferDataToCloudAccountDescription', + { defaultMessage: 'Transfer Data to Cloud Account (T1537)' } + ), + id: 'T1537', + name: 'Transfer Data to Cloud Account', + reference: 'https://attack.mitre.org/techniques/T1537', + tactics: 'exfiltration', + value: 'transferDataToCloudAccount', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.transmittedDataManipulationDescription', + { defaultMessage: 'Transmitted Data Manipulation (T1493)' } + ), + id: 'T1493', + name: 'Transmitted Data Manipulation', + reference: 'https://attack.mitre.org/techniques/T1493', + tactics: 'impact', + value: 'transmittedDataManipulation', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.trapDescription', { + defaultMessage: 'Trap (T1154)', + }), + id: 'T1154', + name: 'Trap', + reference: 'https://attack.mitre.org/techniques/T1154', + tactics: 'execution,persistence', + value: 'trap', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedDeveloperUtilitiesDescription', + { defaultMessage: 'Trusted Developer Utilities (T1127)' } + ), + id: 'T1127', + name: 'Trusted Developer Utilities', + reference: 'https://attack.mitre.org/techniques/T1127', + tactics: 'defense-evasion,execution', + value: 'trustedDeveloperUtilities', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.trustedRelationshipDescription', + { defaultMessage: 'Trusted Relationship (T1199)' } + ), + id: 'T1199', + name: 'Trusted Relationship', + reference: 'https://attack.mitre.org/techniques/T1199', + tactics: 'initial-access', + value: 'trustedRelationship', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.twoFactorAuthenticationInterceptionDescription', + { defaultMessage: 'Two-Factor Authentication Interception (T1111)' } + ), + id: 'T1111', + name: 'Two-Factor Authentication Interception', + reference: 'https://attack.mitre.org/techniques/T1111', + tactics: 'credential-access', + value: 'twoFactorAuthenticationInterception', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.uncommonlyUsedPortDescription', + { defaultMessage: 'Uncommonly Used Port (T1065)' } + ), + id: 'T1065', + name: 'Uncommonly Used Port', + reference: 'https://attack.mitre.org/techniques/T1065', + tactics: 'command-and-control', + value: 'uncommonlyUsedPort', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.unusedUnsupportedCloudRegionsDescription', + { defaultMessage: 'Unused/Unsupported Cloud Regions (T1535)' } + ), + id: 'T1535', + name: 'Unused/Unsupported Cloud Regions', + reference: 'https://attack.mitre.org/techniques/T1535', + tactics: 'defense-evasion', + value: 'unusedUnsupportedCloudRegions', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.userExecutionDescription', + { defaultMessage: 'User Execution (T1204)' } + ), + id: 'T1204', + name: 'User Execution', + reference: 'https://attack.mitre.org/techniques/T1204', + tactics: 'execution', + value: 'userExecution', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.validAccountsDescription', + { defaultMessage: 'Valid Accounts (T1078)' } + ), + id: 'T1078', + name: 'Valid Accounts', + reference: 'https://attack.mitre.org/techniques/T1078', + tactics: 'defense-evasion,persistence,privilege-escalation,initial-access', + value: 'validAccounts', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.videoCaptureDescription', + { defaultMessage: 'Video Capture (T1125)' } + ), + id: 'T1125', + name: 'Video Capture', + reference: 'https://attack.mitre.org/techniques/T1125', + tactics: 'collection', + value: 'videoCapture', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.virtualizationSandboxEvasionDescription', + { defaultMessage: 'Virtualization/Sandbox Evasion (T1497)' } + ), + id: 'T1497', + name: 'Virtualization/Sandbox Evasion', + reference: 'https://attack.mitre.org/techniques/T1497', + tactics: 'defense-evasion,discovery', + value: 'virtualizationSandboxEvasion', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.webServiceDescription', + { defaultMessage: 'Web Service (T1102)' } + ), + id: 'T1102', + name: 'Web Service', + reference: 'https://attack.mitre.org/techniques/T1102', + tactics: 'command-and-control,defense-evasion', + value: 'webService', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.webSessionCookieDescription', + { defaultMessage: 'Web Session Cookie (T1506)' } + ), + id: 'T1506', + name: 'Web Session Cookie', + reference: 'https://attack.mitre.org/techniques/T1506', + tactics: 'defense-evasion,lateral-movement', + value: 'webSessionCookie', + }, + { + label: i18n.translate('xpack.siem.detectionEngine.mitreAttackTechniques.webShellDescription', { + defaultMessage: 'Web Shell (T1100)', + }), + id: 'T1100', + name: 'Web Shell', + reference: 'https://attack.mitre.org/techniques/T1100', + tactics: 'persistence,privilege-escalation', + value: 'webShell', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsAdminSharesDescription', + { defaultMessage: 'Windows Admin Shares (T1077)' } + ), + id: 'T1077', + name: 'Windows Admin Shares', + reference: 'https://attack.mitre.org/techniques/T1077', + tactics: 'lateral-movement', + value: 'windowsAdminShares', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationDescription', + { defaultMessage: 'Windows Management Instrumentation (T1047)' } + ), + id: 'T1047', + name: 'Windows Management Instrumentation', + reference: 'https://attack.mitre.org/techniques/T1047', + tactics: 'execution', + value: 'windowsManagementInstrumentation', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsManagementInstrumentationEventSubscriptionDescription', + { defaultMessage: 'Windows Management Instrumentation Event Subscription (T1084)' } + ), + id: 'T1084', + name: 'Windows Management Instrumentation Event Subscription', + reference: 'https://attack.mitre.org/techniques/T1084', + tactics: 'persistence', + value: 'windowsManagementInstrumentationEventSubscription', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.windowsRemoteManagementDescription', + { defaultMessage: 'Windows Remote Management (T1028)' } + ), + id: 'T1028', + name: 'Windows Remote Management', + reference: 'https://attack.mitre.org/techniques/T1028', + tactics: 'execution,lateral-movement', + value: 'windowsRemoteManagement', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.winlogonHelperDllDescription', + { defaultMessage: 'Winlogon Helper DLL (T1004)' } + ), + id: 'T1004', + name: 'Winlogon Helper DLL', + reference: 'https://attack.mitre.org/techniques/T1004', + tactics: 'persistence', + value: 'winlogonHelperDll', + }, + { + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.xslScriptProcessingDescription', + { defaultMessage: 'XSL Script Processing (T1220)' } + ), + id: 'T1220', + name: 'XSL Script Processing', + reference: 'https://attack.mitre.org/techniques/T1220', + tactics: 'defense-evasion,execution', + value: 'xslScriptProcessing', + }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts new file mode 100644 index 0000000000000..a1e7a2e66ab83 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/mitre/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface MitreOptions { + id: string; + name: string; + reference: string; + value: string; +} + +export interface MitreTacticsOptions extends MitreOptions { + text: string; +} + +export interface MitreTechniquesOptions extends MitreOptions { + label: string; + tactics: string; +} diff --git a/x-pack/legacy/plugins/siem/scripts/extract_tactics_techniques_mitre.js b/x-pack/legacy/plugins/siem/scripts/extract_tactics_techniques_mitre.js new file mode 100644 index 0000000000000..9648acecfe461 --- /dev/null +++ b/x-pack/legacy/plugins/siem/scripts/extract_tactics_techniques_mitre.js @@ -0,0 +1,113 @@ +/* + * 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. + */ + +require('../../../../../src/setup_node_env'); + +const fs = require('fs'); +// eslint-disable-next-line import/no-extraneous-dependencies +const fetch = require('node-fetch'); +const { camelCase } = require('lodash'); +const { resolve } = require('path'); + + +const OUTPUT_DIRECTORY = resolve('public', 'pages', 'detection_engine', 'mitre'); +const MITRE_ENTREPRISE_ATTACK_URL = 'https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json'; + + + +const getTacticsOptions = tactics => tactics.map(t => `{ + id: '${t.id}', + name: '${t.name}', + reference: '${t.reference}', + text: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTactics.${camelCase(t.name)}Description', { + defaultMessage: '${t.name} (${t.id})' + }), + value: '${camelCase(t.name)}' +}`.replace(/(\r\n|\n|\r)/gm, ' ')); + +const getTechniquesOptions = techniques => techniques.map(t => `{ + label: i18n.translate( + 'xpack.siem.detectionEngine.mitreAttackTechniques.${camelCase(t.name)}Description', { + defaultMessage: '${t.name} (${t.id})' + }), + id: '${t.id}', + name: '${t.name}', + reference: '${t.reference}', + tactics: '${t.tactics.join()}', + value: '${camelCase(t.name)}' +}`.replace(/(\r\n|\n|\r)/gm, ' ')); + +const getIdReference = references => references.reduce((obj, extRef) => { + if (extRef.source_name === 'mitre-attack') { + return { + id: extRef.external_id, reference: extRef.url + }; + } + return obj; +}, { id: '', reference: '' }); + +async function main() { + fetch(MITRE_ENTREPRISE_ATTACK_URL) + .then(res => res.json()) + .then(json => { + const mitreData = json.objects; + const tactics = mitreData.filter(obj => obj.type === 'x-mitre-tactic').reduce((acc, item) => { + const { id, reference } = getIdReference(item.external_references); + + return [...acc, { + name: item.name, + id, + reference, + }]; + }, []); + const techniques = mitreData.filter(obj => obj.type === 'attack-pattern').reduce((acc, item) => { + let tactics = []; + const { id, reference } = getIdReference(item.external_references); + if (item.kill_chain_phases != null && item.kill_chain_phases.length > 0) { + item.kill_chain_phases.forEach(tactic => { + tactics = [...tactics, tactic.phase_name]; + }); + } + + return [...acc, { + name: item.name, + id, + reference, + tactics, + }]; + }, []); + + const body = + `/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + import { i18n } from '@kbn/i18n'; + + import { MitreTacticsOptions, MitreTechniquesOptions } from './types'; + + export const tactics = ${JSON.stringify(tactics, null, 2)}; + + export const tacticsOptions: MitreTacticsOptions[] = + ${JSON.stringify(getTacticsOptions(tactics), null, 2).replace(/}"/g, '}').replace(/"{/g, '{')}; + + export const techniques = ${JSON.stringify(techniques, null, 2)}; + + export const techniquesOptions: MitreTechniquesOptions[] = + ${JSON.stringify(getTechniquesOptions(techniques), null, 2).replace(/}"/g, '}').replace(/"{/g, '{')}; + `; + + fs.writeFileSync(`${OUTPUT_DIRECTORY}/mitre_tactics_techniques.ts`, body, 'utf-8'); + + }); +} + +if (require.main === module) { + main(); +} From e71deb26832d65a9f488f7c2ab1ce73712b5e629 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 10 Dec 2019 09:24:13 -0700 Subject: [PATCH 37/56] [Reporting/Screenshots] Do not fail the report if request is aborted (#52344) * [Reporting/Screenshots] Do not fail the report if request is aborted * take pageRequestFailed out of pageExit observable --- .../browsers/chromium/driver_factory/index.ts | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index ca26f7d41c12a..daa7df343f8aa 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -15,7 +15,7 @@ import { } from 'puppeteer'; import del from 'del'; import * as Rx from 'rxjs'; -import { ignoreElements, mergeMap, tap } from 'rxjs/operators'; +import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators'; import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; import { BrowserConfig, NetworkPolicy } from '../../../../types'; @@ -144,7 +144,7 @@ export class HeadlessChromiumDriverFactory { terminate$ .pipe( tap(signal => { - this.logger.debug(`Observer got signal: ${signal}`); + this.logger.debug(`Termination signal received: ${signal}`); }), ignoreElements() ) @@ -156,7 +156,6 @@ export class HeadlessChromiumDriverFactory { this.getProcessLogger(browser).subscribe(); const driver$ = Rx.of(new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect, networkPolicy: this.networkPolicy })); // prettier-ignore - const exit$ = this.getPageExit(browser, page); observer.next({ driver$, exit$ }); @@ -173,9 +172,9 @@ export class HeadlessChromiumDriverFactory { }); } - getBrowserLogger(page: Page): Rx.Observable { - return Rx.fromEvent(page, 'console').pipe( - tap(line => { + getBrowserLogger(page: Page): Rx.Observable { + const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( + map(line => { if (line.type() === 'error') { this.logger.error(line.text(), ['headless-browser-console']); } else { @@ -183,6 +182,19 @@ export class HeadlessChromiumDriverFactory { } }) ); + + const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( + map(req => { + const failure = req.failure && req.failure(); + if (failure) { + this.logger.warning( + `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` + ); + } + }) + ); + + return Rx.merge(consoleMessages$, pageRequestFailed$); } getProcessLogger(browser: Browser) { @@ -208,18 +220,6 @@ export class HeadlessChromiumDriverFactory { mergeMap(err => Rx.throwError(err)) ); - const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( - mergeMap(req => { - const failure = req.failure && req.failure(); - if (failure) { - return Rx.throwError( - new Error(`Request to [${req.url()}] failed! [${failure.errorText}]`) - ); - } - return Rx.throwError(new Error(`Unknown failure!`)); - }) - ); - const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( mergeMap(() => Rx.throwError( @@ -230,11 +230,6 @@ export class HeadlessChromiumDriverFactory { ) ); - return Rx.merge( - pageError$, - uncaughtExceptionPageError$, - pageRequestFailed$, - browserDisconnect$ - ); + return Rx.merge(pageError$, uncaughtExceptionPageError$, browserDisconnect$); } } From 618e70433b8455322be08b9175dfcf66ba5d80be Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 10 Dec 2019 17:32:56 +0100 Subject: [PATCH 38/56] Shim oss telemetry (#51168) --- .../legacy/plugins/oss_telemetry/index.d.ts | 61 ------------------- x-pack/legacy/plugins/oss_telemetry/index.js | 24 -------- x-pack/legacy/plugins/oss_telemetry/index.ts | 41 +++++++++++++ .../server/lib/collectors/index.ts | 7 +-- .../get_usage_collector.test.ts | 46 +++++--------- .../visualizations/get_usage_collector.ts | 16 +++-- .../register_usage_collector.ts | 10 +-- .../oss_telemetry/server/lib/tasks/index.ts | 47 ++++++++++---- .../tasks/visualizations/task_runner.test.ts | 23 ++++--- .../lib/tasks/visualizations/task_runner.ts | 48 ++++++++------- .../plugins/oss_telemetry/server/plugin.ts | 50 +++++++++++++++ .../plugins/oss_telemetry/test_utils/index.ts | 51 ++++++++-------- 12 files changed, 223 insertions(+), 201 deletions(-) delete mode 100644 x-pack/legacy/plugins/oss_telemetry/index.d.ts delete mode 100644 x-pack/legacy/plugins/oss_telemetry/index.js create mode 100644 x-pack/legacy/plugins/oss_telemetry/index.ts create mode 100644 x-pack/legacy/plugins/oss_telemetry/server/plugin.ts diff --git a/x-pack/legacy/plugins/oss_telemetry/index.d.ts b/x-pack/legacy/plugins/oss_telemetry/index.d.ts deleted file mode 100644 index 1b592dabf2053..0000000000000 --- a/x-pack/legacy/plugins/oss_telemetry/index.d.ts +++ /dev/null @@ -1,61 +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 interface VisState { - type: string; -} - -export interface Visualization { - visState: string; -} - -export interface SavedObjectDoc { - _id: string; - _source: { - visualization: Visualization; - type: string; - }; -} - -export interface ESQueryResponse { - hits: { - hits: SavedObjectDoc[]; - }; -} - -export interface TaskInstance { - state: { - runs: number; - stats: any; - }; - error?: any; -} - -export interface HapiServer { - plugins: { - xpack_main: any; - elasticsearch: { - getCluster: ( - cluster: string - ) => { - callWithInternalUser: () => Promise; - }; - }; - task_manager: { - registerTaskDefinitions: (opts: any) => void; - ensureScheduled: (opts: any) => Promise; - fetch: ( - opts: any - ) => Promise<{ - docs: TaskInstance[]; - }>; - }; - }; - config: () => { - get: (prop: string) => any; - }; - log: (context: string[], message: string) => void; -} diff --git a/x-pack/legacy/plugins/oss_telemetry/index.js b/x-pack/legacy/plugins/oss_telemetry/index.js deleted file mode 100644 index f86baef020aa2..0000000000000 --- a/x-pack/legacy/plugins/oss_telemetry/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerCollectors } from './server/lib/collectors'; -import { registerTasks, scheduleTasks } from './server/lib/tasks'; -import { PLUGIN_ID } from './constants'; - -export const ossTelemetry = (kibana) => { - return new kibana.Plugin({ - id: PLUGIN_ID, - require: ['elasticsearch', 'xpack_main'], - configPrefix: 'xpack.oss_telemetry', - - init(server) { - const { usageCollection } = server.newPlatform.setup.plugins; - registerCollectors(usageCollection, server); - registerTasks(server); - scheduleTasks(server); - } - }); -}; diff --git a/x-pack/legacy/plugins/oss_telemetry/index.ts b/x-pack/legacy/plugins/oss_telemetry/index.ts new file mode 100644 index 0000000000000..8b16c7cf13cad --- /dev/null +++ b/x-pack/legacy/plugins/oss_telemetry/index.ts @@ -0,0 +1,41 @@ +/* + * 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 { Logger, PluginInitializerContext } from 'kibana/server'; +import { PLUGIN_ID } from './constants'; +import { OssTelemetryPlugin } from './server/plugin'; +import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; + +export const ossTelemetry: LegacyPluginInitializer = kibana => { + return new kibana.Plugin({ + id: PLUGIN_ID, + require: ['elasticsearch', 'xpack_main'], + configPrefix: 'xpack.oss_telemetry', + + init(server) { + const plugin = new OssTelemetryPlugin({ + logger: { + get: () => + ({ + info: (message: string) => server.log(['info', 'task_manager'], message), + debug: (message: string) => server.log(['debug', 'task_manager'], message), + warn: (message: string) => server.log(['warn', 'task_manager'], message), + error: (message: string) => server.log(['error', 'task_manager'], message), + } as Logger), + }, + } as PluginInitializerContext); + plugin.setup(server.newPlatform.setup.core, { + usageCollection: server.newPlatform.setup.plugins.usageCollection, + taskManager: server.plugins.task_manager, + __LEGACY: { + config: server.config(), + xpackMainStatus: ((server.plugins.xpack_main as unknown) as { status: any }).status + .plugin, + }, + }); + }, + }); +}; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts index 0121ed4304d26..3b47099fdc462 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { HapiServer } from '../../../'; import { registerVisualizationsCollector } from './visualizations/register_usage_collector'; +import { OssTelemetrySetupDependencies } from '../../plugin'; -export function registerCollectors(usageCollection: UsageCollectionSetup, server: HapiServer) { - registerVisualizationsCollector(usageCollection, server); +export function registerCollectors(deps: OssTelemetrySetupDependencies) { + registerVisualizationsCollector(deps.usageCollection, deps.taskManager); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts index d316562c826d6..ec35266646650 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts @@ -4,24 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import sinon from 'sinon'; -import { HapiServer } from '../../../../'; -import { - getMockCallWithInternal, - getMockKbnServer, - getMockTaskFetch, -} from '../../../../test_utils'; +import { getMockTaskFetch, getMockTaskManager } from '../../../../test_utils'; import { getUsageCollector } from './get_usage_collector'; describe('getVisualizationsCollector#fetch', () => { - let mockKbnServer: HapiServer; - - beforeEach(() => { - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), getMockTaskFetch()); - }); - test('can return empty stats', async () => { - const { type, fetch } = getUsageCollector(mockKbnServer); + const { type, fetch } = getUsageCollector(getMockTaskManager()); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({}); @@ -34,11 +22,11 @@ describe('getVisualizationsCollector#fetch', () => { runs: 1, stats: { comic_books: { total: 16, max: 12, min: 2, avg: 6 } }, }, + taskType: 'test', + params: {}, }, ]); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { type, fetch } = getUsageCollector(mockKbnServer); + const { type, fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({ comic_books: { avg: 6, max: 12, min: 2, total: 16 } }); @@ -46,23 +34,21 @@ describe('getVisualizationsCollector#fetch', () => { describe('Error handling', () => { test('Silently handles Task Manager NotInitialized', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects( - new Error('NotInitialized taskManager is still waiting for plugins to load') - ); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { fetch } = getUsageCollector(mockKbnServer); - await expect(fetch()).resolves.toBe(undefined); + const mockTaskFetch = jest.fn(() => { + throw new Error('NotInitialized taskManager is still waiting for plugins to load'); + }); + const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + const result = await fetch(); + expect(result).toBe(undefined); }); // In real life, the CollectorSet calls fetch and handles errors test('defers the errors', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects(new Error('BOOM')); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + const mockTaskFetch = jest.fn(() => { + throw new Error('BOOM'); + }); - const { fetch } = getUsageCollector(mockKbnServer); - await expect(fetch()).rejects.toMatchObject(new Error('BOOM')); + const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + await expect(fetch()).rejects.toThrowErrorMatchingInlineSnapshot(`"BOOM"`); }); }); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts index 63640c87f80a6..680cb97e0fda3 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts @@ -5,17 +5,15 @@ */ import { get } from 'lodash'; -import { HapiServer } from '../../../../'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK, VIS_USAGE_TYPE } from '../../../../constants'; -async function isTaskManagerReady(server: HapiServer) { - const result = await fetch(server); +async function isTaskManagerReady(taskManager: TaskManagerPluginSetupContract | undefined) { + const result = await fetch(taskManager); return result !== null; } -async function fetch(server: HapiServer) { - const taskManager = server.plugins.task_manager; - +async function fetch(taskManager: TaskManagerPluginSetupContract | undefined) { if (!taskManager) { return null; } @@ -40,12 +38,12 @@ async function fetch(server: HapiServer) { return docs; } -export function getUsageCollector(server: HapiServer) { +export function getUsageCollector(taskManager: TaskManagerPluginSetupContract | undefined) { let isCollectorReady = false; async function determineIfTaskManagerIsReady() { let isReady = false; try { - isReady = await isTaskManagerReady(server); + isReady = await isTaskManagerReady(taskManager); } catch (err) {} // eslint-disable-line if (isReady) { @@ -60,7 +58,7 @@ export function getUsageCollector(server: HapiServer) { type: VIS_USAGE_TYPE, isReady: () => isCollectorReady, fetch: async () => { - const docs = await fetch(server); + const docs = await fetch(taskManager); // get the accumulated state from the recurring task return get(docs, '[0].state.stats'); }, diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts index 09843a6f87ad7..1a47f68adcc58 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts @@ -5,13 +5,13 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { HapiServer } from '../../../../'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; import { getUsageCollector } from './get_usage_collector'; export function registerVisualizationsCollector( - usageCollection: UsageCollectionSetup, - server: HapiServer + collectorSet: UsageCollectionSetup, + taskManager: TaskManagerPluginSetupContract | undefined ): void { - const collector = usageCollection.makeUsageCollector(getUsageCollector(server)); - usageCollection.registerCollector(collector); + const collector = collectorSet.makeUsageCollector(getUsageCollector(taskManager)); + collectorSet.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts index 16e83a7938e60..cb6b4eab09741 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -4,15 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HapiServer } from '../../../'; +import { CoreSetup, Logger } from 'kibana/server'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK } from '../../../constants'; import { visualizationsTaskRunner } from './visualizations/task_runner'; +import KbnServer from '../../../../../../../src/legacy/server/kbn_server'; +import { LegacyConfig } from '../../plugin'; +import { TaskInstance } from '../../../../task_manager'; -export function registerTasks(server: HapiServer) { - const taskManager = server.plugins.task_manager; - +export function registerTasks({ + taskManager, + logger, + elasticsearch, + config, +}: { + taskManager?: TaskManagerPluginSetupContract; + logger: Logger; + elasticsearch: CoreSetup['elasticsearch']; + config: LegacyConfig; +}) { if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); + logger.debug('Task manager is not available'); return; } @@ -20,18 +32,30 @@ export function registerTasks(server: HapiServer) { [VIS_TELEMETRY_TASK]: { title: 'X-Pack telemetry calculator for Visualizations', type: VIS_TELEMETRY_TASK, - createTaskRunner({ taskInstance }: { taskInstance: any }) { + createTaskRunner({ taskInstance }: { taskInstance: TaskInstance }) { return { - run: visualizationsTaskRunner(taskInstance, server), + run: visualizationsTaskRunner(taskInstance, config, elasticsearch), }; }, }, }); } -export function scheduleTasks(server: HapiServer) { - const taskManager = server.plugins.task_manager; - const { kbnServer } = server.plugins.xpack_main.status.plugin; +export function scheduleTasks({ + taskManager, + xpackMainStatus, + logger, +}: { + taskManager?: TaskManagerPluginSetupContract; + xpackMainStatus: { kbnServer: KbnServer }; + logger: Logger; +}) { + if (!taskManager) { + logger.debug('Task manager is not available'); + return; + } + + const { kbnServer } = xpackMainStatus; kbnServer.afterPluginsInit(() => { // The code block below can't await directly within "afterPluginsInit" @@ -46,9 +70,10 @@ export function scheduleTasks(server: HapiServer) { id: `${PLUGIN_ID}-${VIS_TELEMETRY_TASK}`, taskType: VIS_TELEMETRY_TASK, state: { stats: {}, runs: 0 }, + params: {}, }); } catch (e) { - server.log(['debug', 'telemetry'], `Error scheduling task, received ${e.message}`); + logger.debug(`Error scheduling task, received ${e.message}`); } })(); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index 5db08ed291d6d..0663a5bd330ca 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -5,28 +5,30 @@ */ import moment from 'moment'; -import { HapiServer, TaskInstance } from '../../../../'; import { getMockCallWithInternal, - getMockKbnServer, + getMockConfig, + getMockEs, getMockTaskInstance, } from '../../../../test_utils'; import { visualizationsTaskRunner } from './task_runner'; +import { TaskInstance } from '../../../../../task_manager'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; - let mockKbnServer: HapiServer; beforeEach(() => { mockTaskInstance = getMockTaskInstance(); - mockKbnServer = getMockKbnServer(); }); describe('Error handling', () => { test('catches its own errors', async () => { const mockCallWithInternal = () => Promise.reject(new Error('Things did not go well!')); - mockKbnServer = getMockKbnServer(mockCallWithInternal); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner( + mockTaskInstance, + getMockConfig(), + getMockEs(mockCallWithInternal) + ); const result = await runner(); expect(result).toMatchObject({ error: 'Things did not go well!', @@ -45,7 +47,7 @@ describe('visualizationsTaskRunner', () => { .startOf('day') .toDate(); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner(mockTaskInstance, getMockConfig(), getMockEs()); const result = await runner(); expect(result).toMatchObject({ @@ -123,9 +125,12 @@ describe('visualizationsTaskRunner', () => { }, }, ]); - mockKbnServer = getMockKbnServer(mockCallWithInternal); - const runner = visualizationsTaskRunner(mockTaskInstance, mockKbnServer); + const runner = visualizationsTaskRunner( + mockTaskInstance, + getMockConfig(), + getMockEs(mockCallWithInternal) + ); const result = await runner(); expect(result).toMatchObject({ diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index 3372101c2b457..9d8f76f6a10dc 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -5,15 +5,12 @@ */ import _, { countBy, groupBy, mapValues } from 'lodash'; -import { - ESQueryResponse, - HapiServer, - SavedObjectDoc, - TaskInstance, - VisState, - Visualization, -} from '../../../../'; +import { APICaller, CoreSetup } from 'kibana/server'; import { getNextMidnight } from '../../get_next_midnight'; +import { VisState } from '../../../../../../../../src/legacy/core_plugins/visualizations/public'; +import { TaskInstance } from '../../../../../task_manager'; +import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; +import { LegacyConfig } from '../../../plugin'; interface VisSummary { type: string; @@ -23,7 +20,7 @@ interface VisSummary { /* * Parse the response data into telemetry payload */ -async function getStats(callCluster: (method: string, params: any) => Promise, index: string) { +async function getStats(callCluster: APICaller, index: string) { const searchParams = { size: 10000, // elasticsearch index.max_result_window default value index, @@ -35,24 +32,26 @@ async function getStats(callCluster: (method: string, params: any) => Promise(esResponse, 'hits.hits.length'); if (size < 1) { return; } // `map` to get the raw types - const visSummaries: VisSummary[] = esResponse.hits.hits.map((hit: SavedObjectDoc) => { - const spacePhrases: string[] = hit._id.split(':'); - const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id - const visualization: Visualization = _.get(hit, '_source.visualization', { visState: '{}' }); - const visState: VisState = JSON.parse(visualization.visState); + const visSummaries: VisSummary[] = esResponse.hits.hits.map( + (hit: ESSearchHit<{ visState: string }>) => { + const spacePhrases: string[] = hit._id.split(':'); + const space = spacePhrases.length === 3 ? spacePhrases[0] : 'default'; // if in a custom space, the format of a saved object ID is space:type:id + const visualization = _.get(hit, '_source.visualization', { visState: '{}' }); + const visState: VisState = JSON.parse(visualization.visState); - return { - type: visState.type || '_na_', - space, - }; - }); + return { + type: visState.type || '_na_', + space, + }; + } + ); // organize stats per type const visTypes = groupBy(visSummaries, 'type'); @@ -72,9 +71,12 @@ async function getStats(callCluster: (method: string, params: any) => Promise { diff --git a/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts b/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts new file mode 100644 index 0000000000000..f661311fc24b8 --- /dev/null +++ b/x-pack/legacy/plugins/oss_telemetry/server/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, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; +import { registerCollectors } from './lib/collectors'; +import { registerTasks, scheduleTasks } from './lib/tasks'; +import KbnServer from '../../../../../src/legacy/server/kbn_server'; +import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server'; + +export interface LegacyConfig { + get: (key: string) => string | number | boolean; +} + +export interface OssTelemetrySetupDependencies { + usageCollection: UsageCollectionSetup; + __LEGACY: { + config: LegacyConfig; + xpackMainStatus: { kbnServer: KbnServer }; + }; + taskManager?: TaskManagerPluginSetupContract; +} + +export class OssTelemetryPlugin implements Plugin { + private logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup, deps: OssTelemetrySetupDependencies) { + registerCollectors(deps); + registerTasks({ + taskManager: deps.taskManager, + logger: this.logger, + elasticsearch: core.elasticsearch, + config: deps.__LEGACY.config, + }); + scheduleTasks({ + taskManager: deps.taskManager, + xpackMainStatus: deps.__LEGACY.xpackMainStatus, + logger: this.logger, + }); + } + + public start() {} +} diff --git a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts index 1cebe78b9c7f0..04e248d28b577 100644 --- a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts @@ -4,9 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESQueryResponse, HapiServer, SavedObjectDoc, TaskInstance } from '../'; +import { APICaller, CoreSetup } from 'kibana/server'; -export const getMockTaskInstance = (): TaskInstance => ({ state: { runs: 0, stats: {} } }); +import { TaskInstance } from '../../task_manager'; +import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; + +export const getMockTaskInstance = (): TaskInstance => ({ + state: { runs: 0, stats: {} }, + taskType: 'test', + params: {}, +}); const defaultMockSavedObjects = [ { @@ -20,10 +27,15 @@ const defaultMockSavedObjects = [ const defaultMockTaskDocs = [getMockTaskInstance()]; -export const getMockCallWithInternal = (hits: SavedObjectDoc[] = defaultMockSavedObjects) => { - return (): Promise => { +export const getMockEs = (mockCallWithInternal: APICaller = getMockCallWithInternal()) => + (({ + createClient: () => ({ callAsInternalUser: mockCallWithInternal }), + } as unknown) as CoreSetup['elasticsearch']); + +export const getMockCallWithInternal = (hits: unknown[] = defaultMockSavedObjects): APICaller => { + return ((() => { return Promise.resolve({ hits: { hits } }); - }; + }) as unknown) as APICaller; }; export const getMockTaskFetch = (docs: TaskInstance[] = defaultMockTaskDocs) => { @@ -36,24 +48,13 @@ export const getMockConfig = () => { }; }; -export const getMockKbnServer = ( - mockCallWithInternal = getMockCallWithInternal(), - mockTaskFetch = getMockTaskFetch(), - mockConfig = getMockConfig() -): HapiServer => ({ - plugins: { - elasticsearch: { - getCluster: (cluster: string) => ({ - callWithInternalUser: mockCallWithInternal, - }), - }, - xpack_main: {}, - task_manager: { - registerTaskDefinitions: (opts: any) => undefined, - ensureScheduled: (opts: any) => Promise.resolve(), - fetch: mockTaskFetch, - }, - }, - config: () => mockConfig, - log: () => undefined, +export const getMockTaskManager = (fetch: any = getMockTaskFetch()) => + (({ + registerTaskDefinitions: () => undefined, + ensureScheduled: () => Promise.resolve(), + fetch, + } as unknown) as TaskManagerPluginSetupContract); + +export const getCluster = () => ({ + callWithInternalUser: getMockCallWithInternal(), }); From 3c57f71c3ac2a781b49bf5cab252815d482a6599 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 10 Dec 2019 17:50:00 +0100 Subject: [PATCH 39/56] Removing stateful saved object finder (#52166) --- .../dashboard/dashboard_app_controller.tsx | 11 +- .../kibana/public/dashboard/legacy_imports.ts | 1 - .../open_search_panel.test.js.snap | 4 +- .../components/top_nav/open_search_panel.js | 11 +- .../top_nav/open_search_panel.test.js | 2 +- .../visualize_embeddable_factory.tsx | 3 +- .../kibana/public/visualize/legacy_imports.ts | 1 - .../visualize/listing/visualize_listing.html | 1 + .../visualize/listing/visualize_listing.js | 4 +- .../__snapshots__/new_vis_modal.test.tsx.snap | 2 + .../visualize/wizard/new_vis_modal.test.tsx | 8 ++ .../public/visualize/wizard/new_vis_modal.tsx | 10 +- .../search_selection/search_selection.tsx | 10 +- .../public/visualize/wizard/show_new_vis.tsx | 6 +- .../components/saved_object_finder.tsx | 108 ---------------- .../public/plugin.tsx | 2 +- .../saved_object_finder.test.tsx | 2 +- .../saved_objects/saved_object_finder.tsx | 28 ++++- .../public/np_ready/public/legacy.ts | 3 - .../public/np_ready/public/plugin.tsx | 15 ++- .../renderers/embeddable.tsx | 12 +- .../components/embeddable_flyout/flyout.tsx | 4 +- .../graph/public/components/source_picker.tsx | 4 +- .../new_job/pages/index_or_search/page.tsx | 7 +- .../components/embeddables/embedded_map.tsx | 9 +- .../search_selection/search_selection.tsx | 115 ++++++++++-------- .../legacy/plugins/transform/public/plugin.ts | 13 +- .../legacy/plugins/transform/public/shim.ts | 2 +- 28 files changed, 196 insertions(+), 202 deletions(-) delete mode 100644 src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx 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 3b336ebfc11fe..fd49b26e0d948 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 @@ -36,7 +36,6 @@ import { AppStateClass as TAppStateClass, KbnUrl, SaveOptions, - SavedObjectFinder, unhashUrl, } from './legacy_imports'; import { FilterStateManager, IndexPattern } from '../../../data/public'; @@ -70,6 +69,10 @@ import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../plugins/kibana_react/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; @@ -114,7 +117,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata }, + core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -741,6 +744,10 @@ export class DashboardAppController { }; navActions[TopNavIds.ADD] = () => { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); + openAddPanelFlyout({ embeddable: dashboardContainer, getAllFactories: embeddables.getEmbeddableFactories, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b0f09f0cf9745..af0a833399a52 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -65,4 +65,3 @@ export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_mon export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; -export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap index cc53e4bdcdcf9..2878b11040cf3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -26,7 +26,7 @@ exports[`render 1`] = ` - diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js index 0c3b52fbf0640..ec1763f44f25f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.js @@ -32,11 +32,16 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { SavedObjectFinderUi } from '../../../../../../../plugins/kibana_react/public'; +import { getServices } from '../../kibana_services'; const SEARCH_OBJECT_TYPE = 'search'; export function OpenSearchPanel(props) { + const { + core: { uiSettings, savedObjects }, + } = getServices(); + return ( @@ -50,7 +55,7 @@ export function OpenSearchPanel(props) { - diff --git a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js index ea5c0ef39604d..0c82aeea95294 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/top_nav/open_search_panel.test.js @@ -23,7 +23,7 @@ import { shallow } from 'enzyme'; jest.mock('../../kibana_services', () => { return { getServices: () => ({ - SavedObjectFinder: jest.fn() + core: { uiSettings: {}, savedObjects: {} }, }), }; }); 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 7c9efa280c9f1..a377dafe9e512 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 @@ -199,7 +199,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< editorParams: ['addToDashboard'], }, npStart.core.http.basePath.prepend, - npStart.core.uiSettings + npStart.core.uiSettings, + npStart.core.savedObjects ); } return undefined; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 6adcfd2cc7186..b9909e522b571 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -45,7 +45,6 @@ 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'; 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 4511ac61f7396..4ee8809fab228 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 @@ -16,6 +16,7 @@ vis-types-registry="listingController.visTypeRegistry" add-base-path="listingController.addBasePath" ui-settings="listingController.uiSettings" + saved-objects="listingController.savedObjects" >
    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 9b02be0581b8d..b1ed5ce81d6ee 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 @@ -34,6 +34,7 @@ export function initListingDirective(app) { ['onClose', { watchDepth: 'reference' }], ['addBasePath', { watchDepth: 'reference' }], ['uiSettings', { watchDepth: 'reference' }], + ['savedObjects', { watchDepth: 'reference' }], 'isOpen', ]) ); @@ -54,7 +55,7 @@ export function VisualizeListingController($injector, createNewVis) { toastNotifications, uiSettings, visualizations, - core: { docLinks }, + core: { docLinks, savedObjects }, } = getServices(); const kbnUrl = $injector.get('kbnUrl'); @@ -64,6 +65,7 @@ export function VisualizeListingController($injector, createNewVis) { this.showNewVisModal = false; this.addBasePath = addBasePath; this.uiSettings = uiSettings; + this.savedObjects = savedObjects; this.createNewVis = () => { this.showNewVisModal = true; 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 5be5f58994887..04b7cddc75289 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 @@ -108,6 +108,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` } isOpen={true} onClose={[Function]} + savedObjects={Object {}} uiSettings={ Object { "get": [MockFunction] { @@ -1413,6 +1414,7 @@ exports[`NewVisModal should render as expected 1`] = ` } isOpen={true} onClose={[Function]} + savedObjects={Object {}} uiSettings={ Object { "get": [MockFunction] { 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 0dd2091bbfee0..4eafd06c7bb20 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 @@ -29,6 +29,7 @@ jest.mock('../legacy_imports', () => ({ })); import { NewVisModal } from './new_vis_modal'; +import { SavedObjectsStart } from 'kibana/public'; describe('NewVisModal', () => { const defaultVisTypeParams = { @@ -76,6 +77,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper).toMatchSnapshot(); @@ -89,6 +91,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); @@ -104,6 +107,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,6 +125,7 @@ describe('NewVisModal', () => { editorParams={['foo=true', 'bar=42']} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -138,6 +143,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); @@ -156,6 +162,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); @@ -170,6 +177,7 @@ describe('NewVisModal', () => { visTypesRegistry={visTypes} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={{} as SavedObjectsStart} /> ); 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 0b46b562f2146..0402265610fb1 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,7 +22,7 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { VisType } from '../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; @@ -37,6 +37,7 @@ interface TypeSelectionProps { editorParams?: string[]; addBasePath: (path: string) => string; uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } interface TypeSelectionState { @@ -81,7 +82,12 @@ class NewVisModal extends React.Component - + ) : ( void; visType: VisType; + uiSettings: IUiSettingsClient; + savedObjects: SavedObjectsStart; } export class SearchSelection extends React.Component { @@ -50,7 +54,7 @@ export class SearchSelection extends React.Component { - { }, ]} fixedPageSize={this.fixedPageSize} + uiSettings={this.props.uiSettings} + savedObjects={this.props.savedObjects} /> 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 92320f7bb443a..88838e16c40e2 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 @@ -21,7 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { IUiSettingsClient } from 'kibana/public'; +import { IUiSettingsClient, SavedObjectsStart } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -33,7 +33,8 @@ export function showNewVisModal( visTypeRegistry: TypesStart, { editorParams = [] }: ShowNewVisModalParams = {}, addBasePath: (path: string) => string, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + savedObjects: SavedObjectsStart ) { const container = document.createElement('div'); const onClose = () => { @@ -51,6 +52,7 @@ export function showNewVisModal( editorParams={editorParams} addBasePath={addBasePath} uiSettings={uiSettings} + savedObjects={savedObjects} /> ); diff --git a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx b/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx deleted file mode 100644 index 5b787eb265509..0000000000000 --- a/src/legacy/ui/public/saved_objects/components/saved_object_finder.tsx +++ /dev/null @@ -1,108 +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 React from 'react'; -import { npStart } from 'ui/new_platform'; -import { IconType } from '@elastic/eui'; -import { SavedObjectAttributes } from 'src/core/server'; -import { SimpleSavedObject } from 'src/core/public'; -import { SavedObjectFinder as SavedObjectFinderNP } from '../../../../../plugins/kibana_react/public'; - -/** - * DO NOT USE THIS COMPONENT, IT IS DEPRECATED. - * Use the one in `src/plugins/kibana_react` instead. - */ - -export interface SavedObjectMetaData { - type: string; - name: string; - getIconForSavedObject(savedObject: SimpleSavedObject): IconType; - getTooltipForSavedObject?(savedObject: SimpleSavedObject): string; - showSavedObject?(savedObject: SimpleSavedObject): boolean; -} - -interface BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], - name: string - ) => void; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - noItemsMessage?: React.ReactNode; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - savedObjectMetaData: Array>; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - showFilter?: boolean; -} - -interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: undefined; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize: number; -} - -interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - initialPageSize?: 5 | 10 | 15 | 25; - /** - * @deprecated - * - * Use component in `src/plugins/kibana_react` instead. - */ - fixedPageSize?: undefined; -} -type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; - -export const SavedObjectFinder: React.FC = props => ( - -); diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx index 79cc9b6980545..d18fbba239ec0 100644 --- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx +++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx @@ -27,7 +27,7 @@ import { ExpandPanelAction, ReplacePanelAction } from '.'; import { DashboardContainerFactory } from './embeddable/dashboard_container_factory'; import { Start as InspectorStartContract } from '../../../plugins/inspector/public'; import { - SavedObjectFinder as SavedObjectFinderUi, + SavedObjectFinderUi, SavedObjectFinderProps, ExitFullScreenButton as ExitFullScreenButtonUi, ExitFullScreenButtonProps, diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx index b35ba427378ab..58b396d57639b 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx @@ -35,7 +35,7 @@ import { IconType } from '@elastic/eui'; import { shallow } from 'enzyme'; import React from 'react'; import * as sinon from 'sinon'; -import { SavedObjectFinder } from './saved_object_finder'; +import { SavedObjectFinderUi as SavedObjectFinder } from './saved_object_finder'; // eslint-disable-next-line import { coreMock } from '../../../../core/public/mocks'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx index c65d428958767..51fbbd2ba3046 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx @@ -46,6 +46,7 @@ import { i18n } from '@kbn/i18n'; import { SavedObjectAttributes } from '../../../../core/server'; import { SimpleSavedObject, CoreStart } from '../../../../core/public'; +import { useKibana } from '../context'; // TODO the typings for EuiListGroup are incorrect - maxWidth is missing. This can be removed when the types are adjusted const FixedEuiListGroup = (EuiListGroup as any) as React.FunctionComponent< @@ -104,12 +105,18 @@ interface SavedObjectFinderInitialPageSize extends BaseSavedObjectFinder { initialPageSize?: 5 | 10 | 15 | 25; fixedPageSize?: undefined; } -export type SavedObjectFinderProps = { + +export type SavedObjectFinderProps = SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize; + +export type SavedObjectFinderUiProps = { savedObjects: CoreStart['savedObjects']; uiSettings: CoreStart['uiSettings']; -} & (SavedObjectFinderFixedPage | SavedObjectFinderInitialPageSize); +} & SavedObjectFinderProps; -class SavedObjectFinder extends React.Component { +class SavedObjectFinderUi extends React.Component< + SavedObjectFinderUiProps, + SavedObjectFinderState +> { public static propTypes = { onChoose: PropTypes.func, noItemsMessage: PropTypes.node, @@ -174,7 +181,7 @@ class SavedObjectFinder extends React.Component { + const { services } = useKibana(); + return ( + + ); +}; + +export { SavedObjectFinder, SavedObjectFinderUi }; diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index a310403c86b5d..1928d7ac72313 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -22,7 +22,6 @@ import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; import uiRoutes from 'ui/routes'; // @ts-ignore @@ -39,7 +38,6 @@ export const setup = pluginInstance.setup(npSetup.core, { embeddable: npSetup.plugins.embeddable, inspector: npSetup.plugins.inspector, __LEGACY: { - SavedObjectFinder, ExitFullScreenButton, }, }); @@ -64,7 +62,6 @@ export const start = pluginInstance.start(npStart.core, { inspector: npStart.plugins.inspector, uiActions: npStart.plugins.uiActions, __LEGACY: { - SavedObjectFinder, ExitFullScreenButton, onRenderComplete: (renderCompleteListener: () => void) => { if (rendered) { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 6b82a67b9fcda..adf898d9af2c7 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -38,6 +38,10 @@ import { ContactCardEmbeddableFactory, } from './embeddable_api'; import { App } from './app'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../../src/plugins/kibana_react/public/saved_objects'; import { IEmbeddableStart, IEmbeddableSetup, @@ -47,7 +51,6 @@ export interface SetupDependencies { embeddable: IEmbeddableSetup; inspector: InspectorSetupContract; __LEGACY: { - SavedObjectFinder: React.ComponentType; ExitFullScreenButton: React.ComponentType; }; } @@ -57,7 +60,6 @@ interface StartDependencies { uiActions: IUiActionsStart; inspector: InspectorStartContract; __LEGACY: { - SavedObjectFinder: React.ComponentType; ExitFullScreenButton: React.ComponentType; onRenderComplete: (onRenderComplete: () => void) => void; }; @@ -99,6 +101,13 @@ export class EmbeddableExplorerPublicPlugin plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); ReactDOM.render( , root diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx index 8810871e9161b..5c7ef1a8c1799 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable.tsx @@ -16,8 +16,11 @@ import { } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { start } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; import { EmbeddableExpression } from '../expression_types/embeddable'; -import { SavedObjectFinder } from '../../../../../../src/legacy/ui/public/saved_objects/components/saved_object_finder'; import { RendererStrings } from '../../i18n'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../src/plugins/kibana_react/public'; const { embeddable: strings } = RendererStrings; @@ -34,6 +37,13 @@ interface Handlers { } const renderEmbeddable = (embeddableObject: IEmbeddable, domNode: HTMLElement) => { + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); return (
    { - { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx index 68013bd243a91..cb311f04dd1d7 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx @@ -15,7 +15,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { npStart } from 'ui/new_platform'; +import { SavedObjectFinderUi } from '../../../../../../../../../../src/plugins/kibana_react/public'; export interface PageProps { nextStepPath: string; @@ -46,7 +47,7 @@ export const Page: FC = ({ nextStepPath }) => { - = ({ nextStepPath }) => { }, ]} fixedPageSize={RESULTS_PER_PAGE} + uiSettings={npStart.core.uiSettings} + savedObjects={npStart.core.savedObjects} /> diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index cb73cf73b8d06..1658002408fb0 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -9,7 +9,6 @@ import React, { useEffect, useState } from 'react'; import { createPortalNode, InPortal } from 'react-reverse-portal'; import styled, { css } from 'styled-components'; import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; @@ -29,6 +28,10 @@ import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; import { MapEmbeddable, SetQuery } from './types'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; +import { + SavedObjectFinderProps, + SavedObjectFinderUi, +} from '../../../../../../../src/plugins/kibana_react/public'; interface EmbeddableMapProps { maintainRatio?: boolean; @@ -176,6 +179,10 @@ export const EmbeddedMapComponent = ({ } }, [startDate, endDate]); + const SavedObjectFinder = (props: SavedObjectFinderProps) => ( + + ); + return isError ? null : ( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx index 1a270505d61a6..368c5aa806fe8 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/search_selection/search_selection.tsx @@ -8,8 +8,8 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui' import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; - -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { SavedObjectFinderUi } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { useAppDependencies } from '../../../../app_dependencies'; interface SearchSelectionProps { onSearchSelected: (searchId: string, searchType: string) => void; @@ -17,56 +17,63 @@ interface SearchSelectionProps { const fixedPageSize: number = 8; -export const SearchSelection: FC = ({ onSearchSelected }) => ( - <> - - - {' '} - /{' '} - = ({ onSearchSelected }) => { + const { + core: { uiSettings, savedObjects }, + } = useAppDependencies(); + return ( + <> + + + {' '} + /{' '} + + + + + 'search', + name: i18n.translate( + 'xpack.transform.newTransform.searchSelection.savedObjectType.search', + { + defaultMessage: 'Saved search', + } + ), + }, + { + type: 'index-pattern', + getIconForSavedObject: () => 'indexPatternApp', + name: i18n.translate( + 'xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern', + { + defaultMessage: 'Index pattern', + } + ), + }, + ]} + fixedPageSize={fixedPageSize} + uiSettings={uiSettings} + savedObjects={savedObjects} /> - - - - 'search', - name: i18n.translate( - 'xpack.transform.newTransform.searchSelection.savedObjectType.search', - { - defaultMessage: 'Saved search', - } - ), - }, - { - type: 'index-pattern', - getIconForSavedObject: () => 'indexPatternApp', - name: i18n.translate( - 'xpack.transform.newTransform.searchSelection.savedObjectType.indexPattern', - { - defaultMessage: 'Index pattern', - } - ), - }, - ]} - fixedPageSize={fixedPageSize} - /> - - -); + + + ); +}; diff --git a/x-pack/legacy/plugins/transform/public/plugin.ts b/x-pack/legacy/plugins/transform/public/plugin.ts index e7cc83d16b3b9..08a3a06fc24fc 100644 --- a/x-pack/legacy/plugins/transform/public/plugin.ts +++ b/x-pack/legacy/plugins/transform/public/plugin.ts @@ -27,12 +27,21 @@ const template = `
    ; +export type AppCore = Pick; export interface AppPlugins { management: { From cf28280496c76e58a1b937d16add9aab6350155b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Tue, 10 Dec 2019 19:21:10 +0100 Subject: [PATCH 40/56] [Logs UI] Generalize ML module management (#50662) This abstracts the specific job details out of the ML module management hooks to enable re-use with the upcoming categorization module. closes #50322 --- .../log_analysis/results/log_entry_rate.ts | 12 +- .../http_api/log_analysis/validation/index.ts | 2 +- .../{indices.ts => log_entry_rate_indices.ts} | 14 +- .../common/log_analysis/job_parameters.ts | 18 +- .../analyze_in_ml_button.tsx | 9 +- .../logging/log_analysis_results/index.ts | 7 + .../logs/log_analysis/api/ml_api_types.ts | 27 ++- .../logs/log_analysis/api/ml_cleanup.ts | 39 +++- .../api/ml_get_jobs_summary_api.ts | 10 +- .../log_analysis/api/ml_setup_module_api.ts | 47 ++--- ...tterns_validate.ts => validate_indices.ts} | 14 +- .../containers/logs/log_analysis/index.ts | 7 +- .../log_analysis_capabilities.tsx | 2 +- .../log_analysis/log_analysis_cleanup.tsx | 82 +++----- .../logs/log_analysis/log_analysis_jobs.tsx | 169 ---------------- .../logs/log_analysis/log_analysis_module.tsx | 167 +++++++++++++++ ...ate.tsx => log_analysis_module_status.tsx} | 190 ++++++++++-------- .../log_analysis/log_analysis_module_types.ts | 36 ++++ .../log_analysis/log_analysis_setup_state.tsx | 92 +++++---- .../logs/log_analysis/log_entry_rate.tsx | 50 ----- .../logs/log_analysis/ml_api_types.ts | 28 --- .../infra/public/containers/source/index.ts | 2 +- .../infra/public/containers/source/source.tsx | 1 + .../plugins/infra/public/pages/logs/index.tsx | 4 +- .../first_use.tsx | 0 .../{analysis => log_entry_rate}/index.ts | 0 .../logs/log_entry_rate/module_descriptor.ts | 107 ++++++++++ .../{analysis => log_entry_rate}/page.tsx | 14 +- .../page_content.tsx | 39 ++-- .../page_providers.tsx | 16 +- .../page_results_content.tsx | 79 ++++---- .../page_setup_content.tsx | 46 +++-- .../page_setup_status_unknown.tsx | 2 +- .../page_unavailable_content.tsx | 2 +- .../sections/anomalies/chart.tsx | 0 .../sections/anomalies/expanded_row.tsx | 25 ++- .../sections/anomalies/index.tsx | 6 +- .../sections/anomalies/table.tsx | 4 +- .../sections/helpers/data_formatters.tsx | 26 ++- .../sections/log_rate/bar_chart.tsx | 0 .../sections/log_rate/index.tsx | 2 +- .../service_calls}/get_log_entry_rate.ts | 0 .../setup/index.ts | 0 .../analysis_setup_indices_form.tsx | 17 +- .../analysis_setup_timerange_form.tsx | 0 .../setup/initial_configuration_step/index.ts | 0 .../initial_configuration_step.tsx | 0 .../process_step/create_ml_jobs_button.tsx | 0 .../setup/process_step/index.ts | 0 .../setup/process_step/process_step.tsx | 0 .../process_step/recreate_ml_jobs_button.tsx | 0 .../setup/setup_steps.tsx | 24 ++- .../use_log_entry_rate_module.tsx | 45 +++++ .../use_log_entry_rate_results.ts} | 78 +++---- .../use_log_entry_rate_results_url_state.tsx} | 7 +- .../plugins/infra/server/infra_server.ts | 8 +- .../lib/adapters/framework/adapter_types.ts | 4 +- .../infra/server/routes/log_analysis/index.ts | 2 +- .../log_analysis/results/log_entry_rate.ts | 5 +- .../{index_patterns => validation}/index.ts | 2 +- .../validate.ts => validation/indices.ts} | 25 +-- 61 files changed, 910 insertions(+), 704 deletions(-) rename x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/{indices.ts => log_entry_rate_indices.ts} (74%) rename x-pack/legacy/plugins/infra/public/{pages/logs/analysis/sections => components/logging/log_analysis_results}/analyze_in_ml_button.tsx (96%) create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts rename x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/{index_patterns_validate.ts => validate_indices.ts} (70%) delete mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx rename x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/{log_analysis_status_state.tsx => log_analysis_module_status.tsx} (70%) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts delete mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx delete mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/first_use.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/index.ts (100%) create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/module_descriptor.ts rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page.tsx (53%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_content.tsx (59%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_providers.tsx (53%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_results_content.tsx (78%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_setup_content.tsx (70%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_setup_status_unknown.tsx (92%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/page_unavailable_content.tsx (95%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/anomalies/chart.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/anomalies/expanded_row.tsx (84%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/anomalies/index.tsx (97%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/anomalies/table.tsx (97%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/helpers/data_formatters.tsx (88%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/log_rate/bar_chart.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/sections/log_rate/index.tsx (96%) rename x-pack/legacy/plugins/infra/public/{containers/logs/log_analysis/api => pages/logs/log_entry_rate/service_calls}/get_log_entry_rate.ts (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/index.ts (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/initial_configuration_step/analysis_setup_indices_form.tsx (91%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/initial_configuration_step/analysis_setup_timerange_form.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/initial_configuration_step/index.ts (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/initial_configuration_step/initial_configuration_step.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/process_step/create_ml_jobs_button.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/process_step/index.ts (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/process_step/process_step.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/process_step/recreate_ml_jobs_button.tsx (100%) rename x-pack/legacy/plugins/infra/public/pages/logs/{analysis => log_entry_rate}/setup/setup_steps.tsx (84%) create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_module.tsx rename x-pack/legacy/plugins/infra/public/{containers/logs/log_analysis/log_analysis_results.tsx => pages/logs/log_entry_rate/use_log_entry_rate_results.ts} (58%) rename x-pack/legacy/plugins/infra/public/{containers/logs/log_analysis/log_analysis_results_url_state.tsx => pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx} (96%) rename x-pack/legacy/plugins/infra/server/routes/log_analysis/{index_patterns => validation}/index.ts (89%) rename x-pack/legacy/plugins/infra/server/routes/log_analysis/{index_patterns/validate.ts => validation/indices.ts} (77%) diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts index 5a1412fd8f3d4..dfc3d2aabd11a 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts @@ -29,7 +29,7 @@ export type GetLogEntryRateRequestPayload = rt.TypeOf; + +export const logEntryRateHistogramBucketRT = rt.type({ partitions: rt.array(logEntryRatePartitionRT), startTime: rt.number, }); +export type LogEntryRateHistogramBucket = rt.TypeOf; + export const getLogEntryRateSuccessReponsePayloadRT = rt.type({ data: rt.type({ bucketDuration: rt.number, - histogramBuckets: rt.array(logEntryRateHistogramBucket), + histogramBuckets: rt.array(logEntryRateHistogramBucketRT), totalNumberOfLogEntries: rt.number, }), }); diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts index 727faca69298e..f23ef7ee7c302 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './indices'; +export * from './log_entry_rate_indices'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts similarity index 74% rename from x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts rename to x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts index 62d81dc136853..5b2509074f6ed 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/indices.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/validation/log_entry_rate_indices.ts @@ -6,14 +6,24 @@ import * as rt from 'io-ts'; -export const LOG_ANALYSIS_VALIDATION_INDICES_PATH = '/api/infra/log_analysis/validation/indices'; +export const LOG_ANALYSIS_VALIDATE_INDICES_PATH = + '/api/infra/log_analysis/validation/log_entry_rate_indices'; /** * Request types */ +export const validationIndicesFieldSpecificationRT = rt.type({ + name: rt.string, + validTypes: rt.array(rt.string), +}); + +export type ValidationIndicesFieldSpecification = rt.TypeOf< + typeof validationIndicesFieldSpecificationRT +>; + export const validationIndicesRequestPayloadRT = rt.type({ data: rt.type({ - timestampField: rt.string, + fields: rt.array(validationIndicesFieldSpecificationRT), indices: rt.array(rt.string), }), }); diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts index 5cfe38394a2ce..626e90b65a7d8 100644 --- a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts +++ b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts @@ -4,19 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { JobType } from './log_analysis'; +import * as rt from 'io-ts'; export const bucketSpan = 900000; +export const partitionField = 'event.dataset'; + export const getJobIdPrefix = (spaceId: string, sourceId: string) => `kibana-logs-ui-${spaceId}-${sourceId}-`; -export const getJobId = (spaceId: string, sourceId: string, jobType: JobType) => +export const getJobId = (spaceId: string, sourceId: string, jobType: string) => `${getJobIdPrefix(spaceId, sourceId)}${jobType}`; -export const getDatafeedId = (spaceId: string, sourceId: string, jobType: JobType) => +export const getDatafeedId = (spaceId: string, sourceId: string, jobType: string) => `datafeed-${getJobId(spaceId, sourceId, jobType)}`; -export const getAllModuleJobIds = (spaceId: string, sourceId: string) => [ - getJobId(spaceId, sourceId, 'log-entry-rate'), -]; +export const jobSourceConfigurationRT = rt.type({ + indexPattern: rt.string, + timestampField: rt.string, + bucketSpan: rt.number, +}); + +export type JobSourceConfiguration = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx similarity index 96% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx rename to x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index ef81f229034bd..c5d83e1c205cc 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import url from 'url'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { encode } from 'rison-node'; import chrome from 'ui/chrome'; import { QueryString } from 'ui/utils/query_string'; -import { encode } from 'rison-node'; -import { TimeRange } from '../../../../../common/http_api/shared/time_range'; +import url from 'url'; + +import { TimeRange } from '../../../../common/http_api/shared/time_range'; export const AnalyzeInMlButton: React.FunctionComponent<{ jobId: string; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts new file mode 100644 index 0000000000000..8a4ceb70252a3 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts @@ -0,0 +1,7 @@ +/* + * 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 './analyze_in_ml_button'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts index deb3d528e42c2..9d4d419ceebe3 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts @@ -6,11 +6,30 @@ import * as rt from 'io-ts'; +import { jobSourceConfigurationRT } from '../../../../../common/log_analysis'; + export const jobCustomSettingsRT = rt.partial({ job_revision: rt.number, - logs_source_config: rt.partial({ - indexPattern: rt.string, - timestampField: rt.string, - bucketSpan: rt.number, + logs_source_config: rt.partial(jobSourceConfigurationRT.props), +}); + +export const getMlCapabilitiesResponsePayloadRT = rt.type({ + capabilities: rt.type({ + canGetJobs: rt.boolean, + canCreateJob: rt.boolean, + canDeleteJob: rt.boolean, + canOpenJob: rt.boolean, + canCloseJob: rt.boolean, + canForecastJob: rt.boolean, + canGetDatafeeds: rt.boolean, + canStartStopDatafeed: rt.boolean, + canUpdateJob: rt.boolean, + canUpdateDatafeed: rt.boolean, + canPreviewDatafeed: rt.boolean, }), + isPlatinumOrTrialLicense: rt.boolean, + mlFeatureEnabledInSpace: rt.boolean, + upgradeInProgress: rt.boolean, }); + +export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts index 209da920c4c8b..5054f607fa5dc 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_cleanup.ts @@ -9,17 +9,22 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { kfetch } from 'ui/kfetch'; -import { getAllModuleJobIds, getDatafeedId } from '../../../../../common/log_analysis'; + +import { getDatafeedId, getJobId } from '../../../../../common/log_analysis'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -export const callDeleteJobs = async (spaceId: string, sourceId: string) => { +export const callDeleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { // NOTE: Deleting the jobs via this API will delete the datafeeds at the same time const deleteJobsResponse = await kfetch({ method: 'POST', pathname: '/api/ml/jobs/delete_jobs', body: JSON.stringify( deleteJobsRequestPayloadRT.encode({ - jobIds: getAllModuleJobIds(spaceId, sourceId), + jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), }) ), }); @@ -42,15 +47,24 @@ export const callGetJobDeletionTasks = async () => { ); }; -export const callStopDatafeed = async (spaceId: string, sourceId: string) => { +export const callStopDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { // Stop datafeed due to https://github.com/elastic/kibana/issues/44652 const stopDatafeedResponse = await kfetch({ method: 'POST', - pathname: `/api/ml/datafeeds/${getDatafeedId(spaceId, sourceId, 'log-entry-rate')}/_stop`, + pathname: '/api/ml/jobs/stop_datafeeds', + body: JSON.stringify( + stopDatafeedsRequestPayloadRT.encode({ + datafeedIds: jobTypes.map(jobType => getDatafeedId(spaceId, sourceId, jobType)), + }) + ), }); return pipe( - stopDatafeedResponsePayloadRT.decode(stopDatafeedResponse), + stopDatafeedsResponsePayloadRT.decode(stopDatafeedResponse), fold(throwErrors(createPlainError), identity) ); }; @@ -68,10 +82,19 @@ export const deleteJobsResponsePayloadRT = rt.record( }) ); +export type DeleteJobsResponsePayload = rt.TypeOf; + export const getJobDeletionTasksResponsePayloadRT = rt.type({ jobIds: rt.array(rt.string), }); -export const stopDatafeedResponsePayloadRT = rt.type({ - stopped: rt.boolean, +export const stopDatafeedsRequestPayloadRT = rt.type({ + datafeedIds: rt.array(rt.string), }); + +export const stopDatafeedsResponsePayloadRT = rt.record( + rt.string, + rt.type({ + stopped: rt.boolean, + }) +); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index 6171d10b5f1aa..91e517b0db008 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -12,15 +12,19 @@ import { kfetch } from 'ui/kfetch'; import { jobCustomSettingsRT } from './ml_api_types'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -import { getAllModuleJobIds } from '../../../../../common/log_analysis'; +import { getJobId } from '../../../../../common/log_analysis'; -export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => { +export const callJobsSummaryAPI = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { const response = await kfetch({ method: 'POST', pathname: '/api/ml/jobs/jobs_summary', body: JSON.stringify( fetchJobStatusRequestPayloadRT.encode({ - jobIds: getAllModuleJobIds(spaceId, sourceId), + jobIds: jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)), }) ), }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts index 1c937513c7950..80a4f975cdd57 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts @@ -12,7 +12,6 @@ import { kfetch } from 'ui/kfetch'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getJobIdPrefix } from '../../../../../common/log_analysis'; -import { jobCustomSettingsRT } from './ml_api_types'; export const callSetupMlModuleAPI = async ( moduleId: string, @@ -21,8 +20,8 @@ export const callSetupMlModuleAPI = async ( spaceId: string, sourceId: string, indexPattern: string, - timeField: string, - bucketSpan: number + jobOverrides: SetupMlModuleJobOverrides[] = [], + datafeedOverrides: SetupMlModuleDatafeedOverrides[] = [] ) => { const response = await kfetch({ method: 'POST', @@ -34,25 +33,8 @@ export const callSetupMlModuleAPI = async ( indexPatternName: indexPattern, prefix: getJobIdPrefix(spaceId, sourceId), startDatafeed: true, - jobOverrides: [ - { - job_id: 'log-entry-rate' as const, - analysis_config: { - bucket_span: `${bucketSpan}ms`, - }, - data_description: { - time_field: timeField, - }, - custom_settings: { - logs_source_config: { - indexPattern, - timestampField: timeField, - bucketSpan, - }, - }, - }, - ], - datafeedOverrides: [], + jobOverrides, + datafeedOverrides, }) ), }); @@ -68,23 +50,20 @@ const setupMlModuleTimeParamsRT = rt.partial({ end: rt.number, }); -const setupMlModuleLogEntryRateJobOverridesRT = rt.type({ - job_id: rt.literal('log-entry-rate'), - analysis_config: rt.type({ - bucket_span: rt.string, - }), - data_description: rt.type({ - time_field: rt.string, - }), - custom_settings: jobCustomSettingsRT, -}); +const setupMlModuleJobOverridesRT = rt.object; + +export type SetupMlModuleJobOverrides = rt.TypeOf; + +const setupMlModuleDatafeedOverridesRT = rt.object; + +export type SetupMlModuleDatafeedOverrides = rt.TypeOf; const setupMlModuleRequestParamsRT = rt.type({ indexPatternName: rt.string, prefix: rt.string, startDatafeed: rt.boolean, - jobOverrides: rt.array(setupMlModuleLogEntryRateJobOverridesRT), - datafeedOverrides: rt.array(rt.object), + jobOverrides: rt.array(setupMlModuleJobOverridesRT), + datafeedOverrides: rt.array(setupMlModuleDatafeedOverridesRT), }); const setupMlModuleRequestPayloadRT = rt.intersection([ diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts similarity index 70% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts rename to x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts index 440ee10e4223d..0d2e9b673488e 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/index_patterns_validate.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts @@ -10,20 +10,22 @@ import { identity } from 'fp-ts/lib/function'; import { kfetch } from 'ui/kfetch'; import { - LOG_ANALYSIS_VALIDATION_INDICES_PATH, + LOG_ANALYSIS_VALIDATE_INDICES_PATH, + ValidationIndicesFieldSpecification, validationIndicesRequestPayloadRT, validationIndicesResponsePayloadRT, } from '../../../../../common/http_api'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -export const callIndexPatternsValidate = async (timestampField: string, indices: string[]) => { +export const callValidateIndicesAPI = async ( + indices: string[], + fields: ValidationIndicesFieldSpecification[] +) => { const response = await kfetch({ method: 'POST', - pathname: LOG_ANALYSIS_VALIDATION_INDICES_PATH, - body: JSON.stringify( - validationIndicesRequestPayloadRT.encode({ data: { timestampField, indices } }) - ), + pathname: LOG_ANALYSIS_VALIDATE_INDICES_PATH, + body: JSON.stringify(validationIndicesRequestPayloadRT.encode({ data: { indices, fields } })), }); return pipe( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts index cbe3b2ef1e9b8..eb044c86e50fe 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts @@ -6,7 +6,6 @@ export * from './log_analysis_capabilities'; export * from './log_analysis_cleanup'; -export * from './log_analysis_jobs'; -export * from './log_analysis_results'; -export * from './log_analysis_results_url_state'; -export * from './log_analysis_status_state'; +export * from './log_analysis_module'; +export * from './log_analysis_module_status'; +export * from './log_analysis_module_types'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index 7ac7d051e6783..35a3ac737ada3 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -15,7 +15,7 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { getMlCapabilitiesResponsePayloadRT, GetMlCapabilitiesResponsePayload, -} from './ml_api_types'; +} from './api/ml_api_types'; import { throwErrors, createPlainError } from '../../../../common/runtime_types'; export const useLogAnalysisCapabilities = () => { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx index 1b79d3c1ef786..a37d18cc33cfd 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_cleanup.tsx @@ -4,64 +4,46 @@ * you may not use this file except in compliance with the Elastic License. */ -import createContainer from 'constate'; -import { useMemo } from 'react'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callDeleteJobs, callStopDatafeed, callGetJobDeletionTasks } from './api/ml_cleanup'; -import { getAllModuleJobIds } from '../../../../common/log_analysis'; - -export const useLogAnalysisCleanup = ({ - sourceId, - spaceId, -}: { - sourceId: string; - spaceId: string; -}) => { - const [cleanupMLResourcesRequest, cleanupMLResources] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - try { - await callStopDatafeed(spaceId, sourceId); - } catch (err) { - // Datefeed has been deleted / doesn't exist, proceed with deleting jobs anyway - if (err && err.res && err.res.status === 404) { - return await deleteJobs(spaceId, sourceId); - } else { - throw err; - } - } - - return await deleteJobs(spaceId, sourceId); - }, - }, - [spaceId, sourceId] - ); - - const isCleaningUp = useMemo(() => cleanupMLResourcesRequest.state === 'pending', [ - cleanupMLResourcesRequest.state, - ]); +import { getJobId } from '../../../../common/log_analysis'; +import { callDeleteJobs, callGetJobDeletionTasks, callStopDatafeeds } from './api/ml_cleanup'; + +export const cleanUpJobsAndDatafeeds = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + try { + await callStopDatafeeds(spaceId, sourceId, jobTypes); + } catch (err) { + // Proceed only if datafeed has been deleted or didn't exist in the first place + if (err?.res?.status !== 404) { + throw err; + } + } - return { - cleanupMLResources, - isCleaningUp, - }; + return await deleteJobs(spaceId, sourceId, jobTypes); }; -export const LogAnalysisCleanup = createContainer(useLogAnalysisCleanup); - -const deleteJobs = async (spaceId: string, sourceId: string) => { - const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId); - await waitUntilJobsAreDeleted(spaceId, sourceId); +const deleteJobs = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const deleteJobsResponse = await callDeleteJobs(spaceId, sourceId, jobTypes); + await waitUntilJobsAreDeleted(spaceId, sourceId, jobTypes); return deleteJobsResponse; }; -const waitUntilJobsAreDeleted = async (spaceId: string, sourceId: string) => { +const waitUntilJobsAreDeleted = async ( + spaceId: string, + sourceId: string, + jobTypes: JobType[] +) => { + const moduleJobIds = jobTypes.map(jobType => getJobId(spaceId, sourceId, jobType)); while (true) { - const response = await callGetJobDeletionTasks(); - const jobIdsBeingDeleted = response.jobIds; - const moduleJobIds = getAllModuleJobIds(spaceId, sourceId); + const { jobIds: jobIdsBeingDeleted } = await callGetJobDeletionTasks(); const needToWait = jobIdsBeingDeleted.some(jobId => moduleJobIds.includes(jobId)); + if (needToWait) { await timeout(1000); } else { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx deleted file mode 100644 index 0f386f416b866..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx +++ /dev/null @@ -1,169 +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 createContainer from 'constate'; -import { useMemo, useCallback, useEffect } from 'react'; - -import { callGetMlModuleAPI } from './api/ml_get_module'; -import { bucketSpan, getJobId } from '../../../../common/log_analysis'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; -import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -import { useLogAnalysisCleanup } from './log_analysis_cleanup'; -import { useStatusState } from './log_analysis_status_state'; - -const MODULE_ID = 'logs_ui_analysis'; - -export const useLogAnalysisJobs = ({ - indexPattern, - sourceId, - spaceId, - timeField, -}: { - indexPattern: string; - sourceId: string; - spaceId: string; - timeField: string; -}) => { - const { cleanupMLResources } = useLogAnalysisCleanup({ sourceId, spaceId }); - const [statusState, dispatch] = useStatusState({ - bucketSpan, - indexPattern, - timestampField: timeField, - }); - - const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - dispatch({ type: 'fetchingModuleDefinition' }); - return await callGetMlModuleAPI(MODULE_ID); - }, - onResolve: response => { - dispatch({ - type: 'fetchedModuleDefinition', - spaceId, - sourceId, - moduleDefinition: response, - }); - }, - onReject: () => { - dispatch({ type: 'failedFetchingModuleDefinition' }); - }, - }, - [] - ); - - const [setupMlModuleRequest, setupMlModule] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async ( - indices: string[], - start: number | undefined, - end: number | undefined - ) => { - dispatch({ type: 'startedSetup' }); - return await callSetupMlModuleAPI( - MODULE_ID, - start, - end, - spaceId, - sourceId, - indices.join(','), - timeField, - bucketSpan - ); - }, - onResolve: ({ datafeeds, jobs }: SetupMlModuleResponsePayload) => { - dispatch({ type: 'finishedSetup', datafeeds, jobs, spaceId, sourceId }); - }, - onReject: () => { - dispatch({ type: 'failedSetup' }); - }, - }, - [spaceId, sourceId, timeField, bucketSpan] - ); - - const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - dispatch({ type: 'fetchingJobStatuses' }); - return await callJobsSummaryAPI(spaceId, sourceId); - }, - onResolve: jobResponse => { - dispatch({ type: 'fetchedJobStatuses', payload: jobResponse, spaceId, sourceId }); - }, - onReject: err => { - dispatch({ type: 'failedFetchingJobStatuses' }); - }, - }, - [spaceId, sourceId] - ); - - const isLoadingSetupStatus = useMemo( - () => - fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending', - [fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state] - ); - - const availableIndices = useMemo(() => indexPattern.split(','), [indexPattern]); - - const viewResults = useCallback(() => { - dispatch({ type: 'viewedResults' }); - }, []); - - const cleanupAndSetup = useCallback( - (indices: string[], start: number | undefined, end: number | undefined) => { - dispatch({ type: 'startedSetup' }); - cleanupMLResources() - .then(() => { - setupMlModule(indices, start, end); - }) - .catch(() => { - dispatch({ type: 'failedSetup' }); - }); - }, - [cleanupMLResources, setupMlModule] - ); - - const viewSetupForReconfiguration = useCallback(() => { - dispatch({ type: 'requestedJobConfigurationUpdate' }); - }, []); - - const viewSetupForUpdate = useCallback(() => { - dispatch({ type: 'requestedJobDefinitionUpdate' }); - }, []); - - useEffect(() => { - fetchModuleDefinition(); - }, [fetchModuleDefinition]); - - const jobIds = useMemo(() => { - return { - 'log-entry-rate': getJobId(spaceId, sourceId, 'log-entry-rate'), - }; - }, [sourceId, spaceId]); - - return { - availableIndices, - fetchJobStatus, - isLoadingSetupStatus, - jobStatus: statusState.jobStatus, - lastSetupErrorMessages: statusState.lastSetupErrorMessages, - cleanupAndSetup, - setup: setupMlModule, - setupMlModuleRequest, - setupStatus: statusState.setupStatus, - timestampField: timeField, - viewSetupForReconfiguration, - viewSetupForUpdate, - viewResults, - jobIds, - }; -}; - -export const LogAnalysisJobs = createContainer(useLogAnalysisJobs); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx new file mode 100644 index 0000000000000..189b58d7923f8 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx @@ -0,0 +1,167 @@ +/* + * 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 { useCallback, useMemo } from 'react'; + +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { useModuleStatus } from './log_analysis_module_status'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types'; + +export const useLogAnalysisModule = ({ + sourceConfiguration, + moduleDescriptor, +}: { + sourceConfiguration: ModuleSourceConfiguration; + moduleDescriptor: ModuleDescriptor; +}) => { + const { spaceId, sourceId, timestampField, indices } = sourceConfiguration; + const [moduleStatus, dispatchModuleStatus] = useModuleStatus(moduleDescriptor.jobTypes, { + bucketSpan: moduleDescriptor.bucketSpan, + indexPattern: indices.join(','), + timestampField, + }); + + const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingModuleDefinition' }); + return await moduleDescriptor.getModuleDefinition(); + }, + onResolve: response => { + dispatchModuleStatus({ + type: 'fetchedModuleDefinition', + spaceId, + sourceId, + moduleDefinition: response, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingModuleDefinition' }); + }, + }, + [moduleDescriptor.getModuleDefinition, spaceId, sourceId] + ); + + const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatchModuleStatus({ type: 'fetchingJobStatuses' }); + return await moduleDescriptor.getJobSummary(spaceId, sourceId); + }, + onResolve: jobResponse => { + dispatchModuleStatus({ + type: 'fetchedJobStatuses', + payload: jobResponse, + spaceId, + sourceId, + }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedFetchingJobStatuses' }); + }, + }, + [spaceId, sourceId] + ); + + const isLoadingModuleStatus = useMemo( + () => + fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending', + [fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state] + ); + + const [, setUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async ( + selectedIndices: string[], + start: number | undefined, + end: number | undefined + ) => { + dispatchModuleStatus({ type: 'startedSetup' }); + return await moduleDescriptor.setUpModule(start, end, { + indices: selectedIndices, + sourceId, + spaceId, + timestampField, + }); + }, + onResolve: ({ datafeeds, jobs }) => { + dispatchModuleStatus({ type: 'finishedSetup', datafeeds, jobs, spaceId, sourceId }); + }, + onReject: () => { + dispatchModuleStatus({ type: 'failedSetup' }); + }, + }, + [moduleDescriptor.setUpModule, spaceId, sourceId, timestampField] + ); + + const [cleanUpModuleRequest, cleanUpModule] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await moduleDescriptor.cleanUpModule(spaceId, sourceId); + }, + }, + [spaceId, sourceId] + ); + + const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ + cleanUpModuleRequest.state, + ]); + + const cleanUpAndSetUpModule = useCallback( + (selectedIndices: string[], start: number | undefined, end: number | undefined) => { + dispatchModuleStatus({ type: 'startedSetup' }); + cleanUpModule() + .then(() => { + setUpModule(selectedIndices, start, end); + }) + .catch(() => { + dispatchModuleStatus({ type: 'failedSetup' }); + }); + }, + [cleanUpModule, setUpModule] + ); + + const viewSetupForReconfiguration = useCallback(() => { + dispatchModuleStatus({ type: 'requestedJobConfigurationUpdate' }); + }, []); + + const viewSetupForUpdate = useCallback(() => { + dispatchModuleStatus({ type: 'requestedJobDefinitionUpdate' }); + }, []); + + const viewResults = useCallback(() => { + dispatchModuleStatus({ type: 'viewedResults' }); + }, []); + + const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ + moduleDescriptor.getJobIds, + spaceId, + sourceId, + ]); + + return { + cleanUpAndSetUpModule, + cleanUpModule, + fetchJobStatus, + fetchModuleDefinition, + isCleaningUp, + isLoadingModuleStatus, + jobIds, + jobStatus: moduleStatus.jobStatus, + lastSetupErrorMessages: moduleStatus.lastSetupErrorMessages, + moduleDescriptor, + setUpModule, + setupStatus: moduleStatus.setupStatus, + sourceConfiguration, + viewResults, + viewSetupForReconfiguration, + viewSetupForUpdate, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx similarity index 70% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx rename to x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx index 1f4c924ea3da5..6d634538cd7fe 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx @@ -7,20 +7,19 @@ import { useReducer } from 'react'; import { + JobSourceConfiguration, + JobStatus, + SetupStatus, getDatafeedId, getJobId, isJobStatusWithResults, - JobStatus, - JobType, - jobTypeRT, - SetupStatus, } from '../../../../common/log_analysis'; import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api'; import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module'; import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; import { MandatoryProperty } from '../../../../common/utility_types'; -interface StatusReducerState { +interface StatusReducerState { jobDefinitions: JobDefinition[]; jobStatus: Record; jobSummaries: JobSummary[]; @@ -65,42 +64,60 @@ type StatusReducerAction = | { type: 'requestedJobDefinitionUpdate' } | { type: 'viewedResults' }; -const createInitialState = (sourceConfiguration: JobSourceConfiguration): StatusReducerState => ({ +const createInitialState = ({ + jobTypes, + sourceConfiguration, +}: { + jobTypes: JobType[]; + sourceConfiguration: JobSourceConfiguration; +}): StatusReducerState => ({ jobDefinitions: [], - jobStatus: { - 'log-entry-rate': 'unknown', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), jobSummaries: [], lastSetupErrorMessages: [], setupStatus: 'initializing', sourceConfiguration, }); -function statusReducer(state: StatusReducerState, action: StatusReducerAction): StatusReducerState { +const createStatusReducer = (jobTypes: JobType[]) => ( + state: StatusReducerState, + action: StatusReducerAction +): StatusReducerState => { switch (action.type) { case 'startedSetup': { return { ...state, - jobStatus: { - 'log-entry-rate': 'initializing', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record + ), setupStatus: 'pending', }; } case 'finishedSetup': { const { jobs, datafeeds, spaceId, sourceId } = action; - const nextJobStatus = { - ...state.jobStatus, - 'log-entry-rate': - hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobs) && - hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, 'log-entry-rate'))( - datafeeds - ) - ? ('started' as JobStatus) - : ('failed' as JobStatus), - }; - const nextSetupStatus = Object.values(nextJobStatus).every(jobState => - ['started'].includes(jobState) + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobs) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))(datafeeds) + ? 'started' + : 'failed', + }), + {} as Record + ); + const nextSetupStatus = Object.values(nextJobStatus).every( + jobState => jobState === 'started' ) ? 'succeeded' : 'failed'; @@ -122,10 +139,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): case 'failedSetup': { return { ...state, - jobStatus: { - ...state.jobStatus, - 'log-entry-rate': 'failed', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record + ), setupStatus: 'failed', }; } @@ -140,10 +160,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): const { payload: jobSummaries, spaceId, sourceId } = action; const { jobDefinitions, setupStatus, sourceConfiguration } = state; - const nextJobStatus = { - ...state.jobStatus, - 'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobSummaries), - }; + const nextJobStatus = jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), + }), + {} as Record + ); const nextSetupStatus = getSetupStatus( spaceId, sourceId, @@ -164,10 +187,13 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): return { ...state, setupStatus: 'unknown', - jobStatus: { - ...state.jobStatus, - 'log-entry-rate': 'unknown', - }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record + ), }; } case 'fetchedModuleDefinition': { @@ -230,7 +256,7 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): return state; } } -} +}; const hasSuccessfullyCreatedJob = (jobId: string) => ( jobSetupResponses: SetupMlModuleResponsePayload['jobs'] @@ -281,7 +307,7 @@ const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePay } )[0] || 'missing'; -const getSetupStatus = ( +const getSetupStatus = ( spaceId: string, sourceId: string, sourceConfiguration: JobSourceConfiguration, @@ -289,44 +315,43 @@ const getSetupStatus = ( jobDefinitions: JobDefinition[], jobSummaries: JobSummary[] ) => (previousSetupStatus: SetupStatus) => - Object.entries(everyJobStatus).reduce((setupStatus, [jobType, jobStatus]) => { - if (!jobTypeRT.is(jobType)) { - return setupStatus; - } + Object.entries(everyJobStatus).reduce( + (setupStatus, [jobType, jobStatus]) => { + const jobId = getJobId(spaceId, sourceId, jobType); + const jobDefinition = jobDefinitions.find(({ id }) => id === jobType); - const jobId = getJobId(spaceId, sourceId, jobType); - const jobDefinition = jobDefinitions.find(({ id }) => id === jobType); + if (jobStatus === 'missing') { + return 'required'; + } else if ( + setupStatus === 'required' || + setupStatus === 'requiredForUpdate' || + setupStatus === 'requiredForReconfiguration' + ) { + return setupStatus; + } else if ( + setupStatus === 'skippedButUpdatable' || + (jobDefinition && + !isJobRevisionCurrent( + jobId, + jobDefinition.config.custom_settings.job_revision || 0 + )(jobSummaries)) + ) { + return 'skippedButUpdatable'; + } else if ( + setupStatus === 'skippedButReconfigurable' || + !isJobConfigurationConsistent(jobId, sourceConfiguration)(jobSummaries) + ) { + return 'skippedButReconfigurable'; + } else if (setupStatus === 'hiddenAfterSuccess') { + return setupStatus; + } else if (setupStatus === 'skipped' || isJobStatusWithResults(jobStatus)) { + return 'skipped'; + } - if (jobStatus === 'missing') { - return 'required'; - } else if ( - setupStatus === 'required' || - setupStatus === 'requiredForUpdate' || - setupStatus === 'requiredForReconfiguration' - ) { return setupStatus; - } else if ( - setupStatus === 'skippedButUpdatable' || - (jobDefinition && - !isJobRevisionCurrent( - jobId, - jobDefinition.config.custom_settings.job_revision || 0 - )(jobSummaries)) - ) { - return 'skippedButUpdatable'; - } else if ( - setupStatus === 'skippedButReconfigurable' || - !isJobConfigurationConsistent(jobId, sourceConfiguration)(jobSummaries) - ) { - return 'skippedButReconfigurable'; - } else if (setupStatus === 'hiddenAfterSuccess') { - return setupStatus; - } else if (setupStatus === 'skipped' || isJobStatusWithResults(jobStatus)) { - return 'skipped'; - } - - return setupStatus; - }, previousSetupStatus); + }, + previousSetupStatus + ); const isJobRevisionCurrent = (jobId: string, currentRevision: number) => ( jobSummaries: FetchJobStatusResponsePayload @@ -377,12 +402,13 @@ const isIndexPatternSubset = (indexPatternSubset: string, indexPatternSuperset: const hasError = (value: Value): value is MandatoryProperty => value.error != null; -export const useStatusState = (sourceConfiguration: JobSourceConfiguration) => { - return useReducer(statusReducer, sourceConfiguration, createInitialState); +export const useModuleStatus = ( + jobTypes: JobType[], + sourceConfiguration: JobSourceConfiguration +) => { + return useReducer( + createStatusReducer(jobTypes), + { jobTypes, sourceConfiguration }, + createInitialState + ); }; - -interface JobSourceConfiguration { - bucketSpan: number; - indexPattern: string; - timestampField: string; -} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts new file mode 100644 index 0000000000000..dc9f25b492635 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_types.ts @@ -0,0 +1,36 @@ +/* + * 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 { DeleteJobsResponsePayload } from './api/ml_cleanup'; +import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload } from './api/ml_get_module'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; +import { ValidationIndicesResponsePayload } from '../../../../common/http_api/log_analysis'; + +export interface ModuleDescriptor { + moduleId: string; + jobTypes: JobType[]; + bucketSpan: number; + getJobIds: (spaceId: string, sourceId: string) => Record; + getJobSummary: (spaceId: string, sourceId: string) => Promise; + getModuleDefinition: () => Promise; + setUpModule: ( + start: number | undefined, + end: number | undefined, + sourceConfiguration: ModuleSourceConfiguration + ) => Promise; + cleanUpModule: (spaceId: string, sourceId: string) => Promise; + validateSetupIndices: ( + sourceConfiguration: ModuleSourceConfiguration + ) => Promise; +} + +export interface ModuleSourceConfiguration { + indices: string[]; + sourceId: string; + spaceId: string; + timestampField: string; +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx index c965c50bedccc..275c0194be3b2 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ValidationIndicesError } from '../../../../common/http_api'; import { isExampleDataIndex } from '../../../../common/log_analysis'; -import { - ValidationIndicesError, - ValidationIndicesResponsePayload, -} from '../../../../common/http_api'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callIndexPatternsValidate } from './api/index_patterns_validate'; +import { ModuleDescriptor, ModuleSourceConfiguration } from './log_analysis_module_types'; type SetupHandler = ( indices: string[], @@ -25,53 +22,69 @@ export type ValidationIndicesUIError = | { error: 'NETWORK_ERROR' } | { error: 'TOO_FEW_SELECTED_INDICES' }; -export interface ValidatedIndex { - index: string; - errors: ValidationIndicesError[]; +interface ValidIndex { + validity: 'valid'; + name: string; isSelected: boolean; } -interface AnalysisSetupStateArguments { - availableIndices: string[]; +interface InvalidIndex { + validity: 'invalid'; + name: string; + errors: ValidationIndicesError[]; +} + +export type ValidatedIndex = ValidIndex | InvalidIndex; + +interface AnalysisSetupStateArguments { cleanupAndSetupModule: SetupHandler; + moduleDescriptor: ModuleDescriptor; setupModule: SetupHandler; - timestampField: string; + sourceConfiguration: ModuleSourceConfiguration; } const fourWeeksInMs = 86400000 * 7 * 4; -export const useAnalysisSetupState = ({ - availableIndices, +export const useAnalysisSetupState = ({ cleanupAndSetupModule, + moduleDescriptor: { validateSetupIndices }, setupModule, - timestampField, -}: AnalysisSetupStateArguments) => { + sourceConfiguration, +}: AnalysisSetupStateArguments) => { const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs); const [endTime, setEndTime] = useState(undefined); - // Prepare the validation - const [validatedIndices, setValidatedIndices] = useState( - availableIndices.map(index => ({ - index, - errors: [], - isSelected: false, - })) - ); + const [validatedIndices, setValidatedIndices] = useState([]); + const [validateIndicesRequest, validateIndices] = useTrackedPromise( { cancelPreviousOn: 'resolution', createPromise: async () => { - return await callIndexPatternsValidate(timestampField, availableIndices); + return await validateSetupIndices(sourceConfiguration); }, - onResolve: ({ data }: ValidationIndicesResponsePayload) => { - setValidatedIndices( - availableIndices.map(index => { - const errors = data.errors.filter(error => error.index === index); - return { - index, - errors, - isSelected: errors.length === 0 && !isExampleDataIndex(index), - }; + onResolve: ({ data: { errors } }) => { + setValidatedIndices(previousValidatedIndices => + sourceConfiguration.indices.map(indexName => { + const previousValidatedIndex = previousValidatedIndices.filter( + ({ name }) => name === indexName + )[0]; + const indexValiationErrors = errors.filter(({ index }) => index === indexName); + if (indexValiationErrors.length > 0) { + return { + validity: 'invalid', + name: indexName, + errors: indexValiationErrors, + }; + } else { + return { + validity: 'valid', + name: indexName, + isSelected: + previousValidatedIndex?.validity === 'valid' + ? previousValidatedIndex?.isSelected + : !isExampleDataIndex(indexName), + }; + } }) ); }, @@ -79,7 +92,7 @@ export const useAnalysisSetupState = ({ setValidatedIndices([]); }, }, - [availableIndices, timestampField] + [sourceConfiguration.indices] ); useEffect(() => { @@ -87,7 +100,10 @@ export const useAnalysisSetupState = ({ }, [validateIndices]); const selectedIndexNames = useMemo( - () => validatedIndices.filter(i => i.isSelected).map(i => i.index), + () => + validatedIndices + .filter(index => index.validity === 'valid' && index.isSelected) + .map(i => i.name), [validatedIndices] ); @@ -120,7 +136,9 @@ export const useAnalysisSetupState = ({ } return validatedIndices.reduce((errors, index) => { - return selectedIndexNames.includes(index.index) ? errors.concat(index.errors) : errors; + return index.validity === 'invalid' && selectedIndexNames.includes(index.name) + ? [...errors, ...index.errors] + : errors; }, []); }, [selectedIndexNames, validatedIndices, validateIndicesRequest.state]); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx deleted file mode 100644 index 8b21a7e829894..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx +++ /dev/null @@ -1,50 +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 { useMemo, useState } from 'react'; - -import { GetLogEntryRateSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { callGetLogEntryRateAPI } from './api/get_log_entry_rate'; - -type LogEntryRateResults = GetLogEntryRateSuccessResponsePayload['data']; - -export const useLogEntryRate = ({ - sourceId, - startTime, - endTime, - bucketDuration, -}: { - sourceId: string; - startTime: number; - endTime: number; - bucketDuration: number; -}) => { - const [logEntryRate, setLogEntryRate] = useState(null); - - const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise( - { - cancelPreviousOn: 'resolution', - createPromise: async () => { - return await callGetLogEntryRateAPI(sourceId, startTime, endTime, bucketDuration); - }, - onResolve: response => { - setLogEntryRate(response.data); - }, - }, - [sourceId, startTime, endTime, bucketDuration] - ); - - const isLoading = useMemo(() => getLogEntryRateRequest.state === 'pending', [ - getLogEntryRateRequest.state, - ]); - - return { - getLogEntryRate, - isLoading, - logEntryRate, - }; -}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts deleted file mode 100644 index ee70edc31d49b..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as rt from 'io-ts'; - -export const getMlCapabilitiesResponsePayloadRT = rt.type({ - capabilities: rt.type({ - canGetJobs: rt.boolean, - canCreateJob: rt.boolean, - canDeleteJob: rt.boolean, - canOpenJob: rt.boolean, - canCloseJob: rt.boolean, - canForecastJob: rt.boolean, - canGetDatafeeds: rt.boolean, - canStartStopDatafeed: rt.boolean, - canUpdateJob: rt.boolean, - canUpdateDatafeed: rt.boolean, - canPreviewDatafeed: rt.boolean, - }), - isPlatinumOrTrialLicense: rt.boolean, - mlFeatureEnabledInSpace: rt.boolean, - upgradeInProgress: rt.boolean, -}); - -export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/source/index.ts b/x-pack/legacy/plugins/infra/public/containers/source/index.ts index 9442836f2a6c6..5911decf21774 100644 --- a/x-pack/legacy/plugins/infra/public/containers/source/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/source/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Source, useSource } from './source'; +export * from './source'; diff --git a/x-pack/legacy/plugins/infra/public/containers/source/source.tsx b/x-pack/legacy/plugins/infra/public/containers/source/source.tsx index 955529c9759c4..4729f7aa31f0b 100644 --- a/x-pack/legacy/plugins/infra/public/containers/source/source.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/source/source.tsx @@ -176,3 +176,4 @@ export const useSource = ({ sourceId }: { sourceId: string }) => { }; export const Source = createContainer(useSource); +export const [SourceProvider, useSourceContext] = Source; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 1630de11bbdff..4eddecf732f75 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -21,7 +21,7 @@ import { Source, useSource } from '../../containers/source'; import { StreamPage } from './stream'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; -import { AnalysisPage } from './analysis'; +import { LogEntryRatePage } from './log_entry_rate'; import { useLogAnalysisCapabilities, LogAnalysisCapabilities, @@ -98,7 +98,7 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag - + + jobTypes.reduce( + (accumulatedJobIds, jobType) => ({ + ...accumulatedJobIds, + [jobType]: getJobId(spaceId, sourceId, jobType), + }), + {} as Record + ); + +const getJobSummary = async (spaceId: string, sourceId: string) => { + const response = await callJobsSummaryAPI(spaceId, sourceId, jobTypes); + const jobIds = Object.values(getJobIds(spaceId, sourceId)); + + return response.filter(jobSummary => jobIds.includes(jobSummary.id)); +}; + +const getModuleDefinition = async () => { + return await callGetMlModuleAPI(moduleId); +}; + +const setUpModule = async ( + start: number | undefined, + end: number | undefined, + { spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration +) => { + const indexNamePattern = indices.join(','); + const jobOverrides = [ + { + job_id: 'log-entry-rate' as const, + analysis_config: { + bucket_span: `${bucketSpan}ms`, + }, + data_description: { + time_field: timestampField, + }, + custom_settings: { + logs_source_config: { + indexPattern: indexNamePattern, + timestampField, + bucketSpan, + }, + }, + }, + ]; + + return callSetupMlModuleAPI( + moduleId, + start, + end, + spaceId, + sourceId, + indexNamePattern, + jobOverrides + ); +}; + +const cleanUpModule = async (spaceId: string, sourceId: string) => { + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, jobTypes); +}; + +const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceConfiguration) => { + return await callValidateIndicesAPI(indices, [ + { + name: timestampField, + validTypes: ['date'], + }, + { + name: partitionField, + validTypes: ['keyword'], + }, + ]); +}; + +export const logEntryRateModule: ModuleDescriptor = { + moduleId, + jobTypes, + bucketSpan, + getJobIds, + getJobSummary, + getModuleDefinition, + setUpModule, + cleanUpModule, + validateSetupIndices, +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx similarity index 53% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx index d82da895f9a5a..5ff5cd4db7168 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { ColumnarPage } from '../../../components/page'; -import { AnalysisPageContent } from './page_content'; -import { AnalysisPageProviders } from './page_providers'; +import { LogEntryRatePageContent } from './page_content'; +import { LogEntryRatePageProviders } from './page_providers'; -export const AnalysisPage = () => { +export const LogEntryRatePage = () => { return ( - - - + + + - + ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx similarity index 59% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index f0a26eae25ecb..e62164cb17b2c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -9,34 +9,37 @@ import React, { useContext, useEffect } from 'react'; import { isSetupStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; -import { LogAnalysisCapabilities, LogAnalysisJobs } from '../../../containers/logs/log_analysis'; +import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis'; import { Source } from '../../../containers/source'; -import { AnalysisResultsContent } from './page_results_content'; -import { AnalysisSetupContent } from './page_setup_content'; -import { AnalysisUnavailableContent } from './page_unavailable_content'; -import { AnalysisSetupStatusUnknownContent } from './page_setup_status_unknown'; +import { LogEntryRateResultsContent } from './page_results_content'; +import { LogEntryRateSetupContent } from './page_setup_content'; +import { LogEntryRateUnavailableContent } from './page_unavailable_content'; +import { LogEntryRateSetupStatusUnknownContent } from './page_setup_status_unknown'; +import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; -export const AnalysisPageContent = () => { +export const LogEntryRatePageContent = () => { const { sourceId } = useContext(Source.Context); const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context); const { - availableIndices, - cleanupAndSetup, + cleanUpAndSetUpModule: cleanupAndSetup, fetchJobStatus, + fetchModuleDefinition, lastSetupErrorMessages, - setup, + moduleDescriptor, + setUpModule, setupStatus, - timestampField, + sourceConfiguration, viewResults, - } = useContext(LogAnalysisJobs.Context); + } = useLogEntryRateModuleContext(); useEffect(() => { + fetchModuleDefinition(); fetchJobStatus(); }, []); if (!hasLogAnalysisCapabilites) { - return ; + return ; } else if (setupStatus === 'initializing') { return ( { /> ); } else if (setupStatus === 'unknown') { - return ; + return ; } else if (isSetupStatusWithResults(setupStatus)) { return ( - ); } else { return ( - ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx similarity index 53% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx index fba32f6cbd6d0..67c8ea7660a26 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_providers.tsx @@ -4,24 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; -import { LogAnalysisJobs } from '../../../containers/logs/log_analysis'; -import { Source } from '../../../containers/source'; +import { useSourceContext } from '../../../containers/source'; import { useKibanaSpaceId } from '../../../utils/use_kibana_space_id'; +import { LogEntryRateModuleProvider } from './use_log_entry_rate_module'; -export const AnalysisPageProviders: React.FunctionComponent = ({ children }) => { - const { sourceId, source } = useContext(Source.Context); +export const LogEntryRatePageProviders: React.FunctionComponent = ({ children }) => { + const { sourceId, source } = useSourceContext(); const spaceId = useKibanaSpaceId(); return ( - {children} - + ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx similarity index 78% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 7fa9ff3c93db7..be637bc29a0db 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -17,36 +17,36 @@ import { import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; import euiStyled from '../../../../../../common/eui_styled_components'; import { TimeRange } from '../../../../common/http_api/shared/time_range'; import { bucketSpan } from '../../../../common/log_analysis'; import { LoadingOverlayWrapper } from '../../../components/loading_overlay_wrapper'; -import { - LogAnalysisJobs, - StringTimeRange, - useLogAnalysisResults, - useLogAnalysisResultsUrlState, -} from '../../../containers/logs/log_analysis'; import { useInterval } from '../../../hooks/use_interval'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; import { FirstUseCallout } from './first_use'; import { AnomaliesResults } from './sections/anomalies'; import { LogRateResults } from './sections/log_rate'; +import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; +import { useLogEntryRateResults } from './use_log_entry_rate_results'; +import { + StringTimeRange, + useLogAnalysisResultsUrlState, +} from './use_log_entry_rate_results_url_state'; const JOB_STATUS_POLLING_INTERVAL = 30000; -export const AnalysisResultsContent = ({ +export const LogEntryRateResultsContent = ({ sourceId, isFirstUse, }: { sourceId: string; isFirstUse: boolean; }) => { - useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); - useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_results', delay: 15000 }); const [dateFormat] = useKibanaUiSetting('dateFormat', 'MMMM D, YYYY h:mm A'); @@ -65,32 +65,20 @@ export const AnalysisResultsContent = ({ lastChangedTime: Date.now(), })); - const bucketDuration = useMemo(() => { - // This function takes the current time range in ms, - // works out the bucket interval we'd need to always - // display 100 data points, and then takes that new - // value and works out the nearest multiple of - // 900000 (15 minutes) to it, so that we don't end up with - // jaggy bucket boundaries between the ML buckets and our - // aggregation buckets. - const msRange = moment(queryTimeRange.value.endTime).diff( - moment(queryTimeRange.value.startTime) - ); - const bucketIntervalInMs = msRange / 100; - const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); - const roundedResult = parseInt(Number(result).toFixed(0), 10); - return roundedResult < bucketSpan ? bucketSpan : roundedResult; - }, [queryTimeRange.value.startTime, queryTimeRange.value.endTime]); + const bucketDuration = useMemo( + () => getBucketDuration(queryTimeRange.value.startTime, queryTimeRange.value.endTime), + [queryTimeRange.value.endTime, queryTimeRange.value.startTime] + ); - const { isLoading, logRateResults } = useLogAnalysisResults({ + const { getLogEntryRate, isLoading, logEntryRate } = useLogEntryRateResults({ sourceId, startTime: queryTimeRange.value.startTime, endTime: queryTimeRange.value.endTime, bucketDuration, - lastRequestTime: queryTimeRange.lastChangedTime, }); - const hasResults = useMemo(() => logRateResults && logRateResults.histogramBuckets.length > 0, [ - logRateResults, + + const hasResults = useMemo(() => (logEntryRate?.histogramBuckets?.length ?? 0) > 0, [ + logEntryRate, ]); const handleQueryTimeRangeChange = useCallback( @@ -145,7 +133,11 @@ export const AnalysisResultsContent = ({ viewSetupForReconfiguration, viewSetupForUpdate, jobIds, - } = useContext(LogAnalysisJobs.Context); + } = useLogEntryRateModuleContext(); + + useEffect(() => { + getLogEntryRate(); + }, [getLogEntryRate, queryTimeRange.lastChangedTime]); useInterval(() => { fetchJobStatus(); @@ -168,7 +160,7 @@ export const AnalysisResultsContent = ({ - {logRateResults ? ( + {logEntryRate ? ( - {numeral(logRateResults.totalNumberOfLogEntries).format('0.00a')} + {numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')} ), @@ -210,7 +202,7 @@ export const AnalysisResultsContent = ({ {isFirstUse && !hasResults ? : null} @@ -223,7 +215,7 @@ export const AnalysisResultsContent = ({ jobStatus={jobStatus['log-entry-rate']} viewSetupForReconfiguration={viewSetupForReconfiguration} viewSetupForUpdate={viewSetupForUpdate} - results={logRateResults} + results={logEntryRate} setTimeRange={handleChartTimeRangeChange} setupStatus={setupStatus} timeRange={queryTimeRange.value} @@ -250,6 +242,23 @@ const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ ).valueOf(), }); +/** + * This function takes the current time range in ms, + * works out the bucket interval we'd need to always + * display 100 data points, and then takes that new + * value and works out the nearest multiple of + * 900000 (15 minutes) to it, so that we don't end up with + * jaggy bucket boundaries between the ML buckets and our + * aggregation buckets. + */ +const getBucketDuration = (startTime: number, endTime: number) => { + const msRange = moment(endTime).diff(moment(startTime)); + const bucketIntervalInMs = msRange / 100; + const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); + const roundedResult = parseInt(Number(result).toFixed(0), 10); + return roundedResult < bucketSpan ? bucketSpan : roundedResult; +}; + // This is needed due to the flex-basis: 100% !important; rule that // kicks in on small screens via media queries breaking when using direction="column" export const ResultsContentPage = euiStyled(EuiPage)` diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx similarity index 70% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx index 7ae174c4a7899..6c04404b91231 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_content.tsx @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, + EuiPageContentBody, EuiPageContentHeader, EuiPageContentHeaderSection, - EuiPageContentBody, + EuiSpacer, EuiText, EuiTitle, - EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + import euiStyled from '../../../../../../common/eui_styled_components'; import { SetupStatus } from '../../../../common/log_analysis'; +import { ModuleDescriptor, ModuleSourceConfiguration } from '../../../containers/logs/log_analysis'; import { useTrackPageview } from '../../../hooks/use_track_metric'; -import { AnalysisSetupSteps } from './setup'; +import { LogEntryRateSetupSteps } from './setup'; type SetupHandler = ( indices: string[], @@ -28,32 +30,32 @@ type SetupHandler = ( endTime: number | undefined ) => void; -interface AnalysisSetupContentProps { - availableIndices: string[]; +interface LogEntryRateSetupContentProps { cleanupAndSetup: SetupHandler; errorMessages: string[]; + moduleDescriptor: ModuleDescriptor; setup: SetupHandler; setupStatus: SetupStatus; - timestampField: string; + sourceConfiguration: ModuleSourceConfiguration; viewResults: () => void; } -export const AnalysisSetupContent: React.FunctionComponent = ({ - availableIndices, +export const LogEntryRateSetupContent = ({ cleanupAndSetup, errorMessages, setup, setupStatus, - timestampField, viewResults, -}) => { - useTrackPageview({ app: 'infra_logs', path: 'analysis_setup' }); - useTrackPageview({ app: 'infra_logs', path: 'analysis_setup', delay: 15000 }); + moduleDescriptor, + sourceConfiguration, +}: LogEntryRateSetupContentProps) => { + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_rate_setup', delay: 15000 }); return ( - + - - - + - + ); }; // !important due to https://github.com/elastic/eui/issues/2232 -const AnalysisPageContent = euiStyled(EuiPageContent)` +const LogEntryRateSetupPageContent = euiStyled(EuiPageContent)` max-width: 768px !important; `; -const AnalysisSetupPage = euiStyled(EuiPage)` +const LogEntryRateSetupPage = euiStyled(EuiPage)` height: 100%; `; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx similarity index 92% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx index 953b0841ffe92..4c685bd42b937 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_status_unknown.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_setup_status_unknown.tsx @@ -14,7 +14,7 @@ interface Props { retry: () => void; } -export const AnalysisSetupStatusUnknownContent: React.FunctionComponent = ({ +export const LogEntryRateSetupStatusUnknownContent: React.FunctionComponent = ({ retry, }: Props) => ( = () => ( +export const LogEntryRateUnavailableContent: React.FunctionComponent<{}> = () => ( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/chart.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx similarity index 84% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 0586f5282ddf7..f8a7f12364cf9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -4,38 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiSpacer } from '@elastic/eui'; -import { AnomaliesChart } from './chart'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; + import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { - getLogEntryRateSeriesForPartition, getAnnotationsForPartition, + getLogEntryRateSeriesForPartition, getTotalNumberOfLogEntriesForPartition, } from '../helpers/data_formatters'; -import { AnalyzeInMlButton } from '../analyze_in_ml_button'; +import { AnomaliesChart } from './chart'; export const AnomaliesTableExpandedRow: React.FunctionComponent<{ partitionId: string; topAnomalyScore: number; - results: LogRateResults; + results: LogEntryRateResults; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; }> = ({ results, timeRange, setTimeRange, partitionId, jobId }) => { const logEntryRateSeries = useMemo( () => - results && results.histogramBuckets - ? getLogEntryRateSeriesForPartition(results, partitionId) - : [], + results?.histogramBuckets ? getLogEntryRateSeriesForPartition(results, partitionId) : [], [results, partitionId] ); const anomalyAnnotations = useMemo( () => - results && results.histogramBuckets + results?.histogramBuckets ? getAnnotationsForPartition(results, partitionId) : { warning: [], @@ -47,7 +46,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ ); const totalNumberOfLogEntries = useMemo( () => - results && results.histogramBuckets + results?.histogramBuckets ? getTotalNumberOfLogEntriesForPartition(results, partitionId) : undefined, [results, partitionId] diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx similarity index 97% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx index e870c2d442719..38aa4b068c9e9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx @@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import euiStyled from '../../../../../../../../common/eui_styled_components'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { JobStatus, SetupStatus } from '../../../../../../common/log_analysis'; import { @@ -30,13 +30,13 @@ import { import { AnomaliesChart } from './chart'; import { AnomaliesTable } from './table'; import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status'; -import { AnalyzeInMlButton } from '../analyze_in_ml_button'; +import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; export const AnomaliesResults: React.FunctionComponent<{ isLoading: boolean; jobStatus: JobStatus; - results: LogRateResults | null; + results: LogEntryRateResults | null; setTimeRange: (timeRange: TimeRange) => void; setupStatus: SetupStatus; timeRange: TimeRange; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx similarity index 97% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index c0016d07c290b..2057d75f72354 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -9,7 +9,7 @@ import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { AnomaliesTableExpandedRow } from './expanded_row'; import { formatAnomalyScore, getFriendlyNameForPartitionId } from '../helpers/data_formatters'; import euiStyled from '../../../../../../../../common/eui_styled_components'; @@ -50,7 +50,7 @@ const maxAnomalyScoreColumnName = i18n.translate( ); export const AnomaliesTable: React.FunctionComponent<{ - results: LogRateResults; + results: LogEntryRateResults; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx similarity index 88% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx index 74a3b5f80a577..f9b85fc4e20c2 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx @@ -6,18 +6,19 @@ import { RectAnnotationDatum } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; -export type MLSeverityScoreCategories = 'warning' | 'minor' | 'major' | 'critical'; -type MLSeverityScores = Record; -const ML_SEVERITY_SCORES: MLSeverityScores = { +import { LogEntryRateResults } from '../../use_log_entry_rate_results'; + +const ML_SEVERITY_SCORES = { warning: 3, minor: 25, major: 50, critical: 75, }; -export const getLogEntryRatePartitionedSeries = (results: LogRateResults) => { +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; + +export const getLogEntryRatePartitionedSeries = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ @@ -33,7 +34,7 @@ export const getLogEntryRatePartitionedSeries = (results: LogRateResults) => { ); }; -export const getLogEntryRateCombinedSeries = (results: LogRateResults) => { +export const getLogEntryRateCombinedSeries = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ @@ -50,7 +51,10 @@ export const getLogEntryRateCombinedSeries = (results: LogRateResults) => { ); }; -export const getLogEntryRateSeriesForPartition = (results: LogRateResults, partitionId: string) => { +export const getLogEntryRateSeriesForPartition = ( + results: LogEntryRateResults, + partitionId: string +) => { return results.partitionBuckets[partitionId].buckets.reduce< Array<{ time: number; value: number }> >((buckets, bucket) => { @@ -64,7 +68,7 @@ export const getLogEntryRateSeriesForPartition = (results: LogRateResults, parti }, []); }; -export const getAnnotationsForPartition = (results: LogRateResults, partitionId: string) => { +export const getAnnotationsForPartition = (results: LogEntryRateResults, partitionId: string) => { return results.partitionBuckets[partitionId].buckets.reduce< Record >( @@ -106,13 +110,13 @@ export const getAnnotationsForPartition = (results: LogRateResults, partitionId: }; export const getTotalNumberOfLogEntriesForPartition = ( - results: LogRateResults, + results: LogEntryRateResults, partitionId: string ) => { return results.partitionBuckets[partitionId].totalNumberOfLogEntries; }; -export const getAnnotationsForAll = (results: LogRateResults) => { +export const getAnnotationsForAll = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (annotatedBucketsBySeverity, bucket) => { const maxAnomalyScoresByPartition = bucket.partitions.reduce< @@ -169,7 +173,7 @@ export const getAnnotationsForAll = (results: LogRateResults) => { ); }; -export const getTopAnomalyScoreAcrossAllPartitions = (results: LogRateResults) => { +export const getTopAnomalyScoreAcrossAllPartitions = (results: LogEntryRateResults) => { const allTopScores = Object.values(results.partitionBuckets).reduce( (scores: number[], partition) => { return [...scores, partition.topAnomalyScore]; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/bar_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/bar_chart.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx similarity index 96% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx index 44805520f3b9e..a11dc9d4d607a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/index.tsx @@ -8,7 +8,7 @@ import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiTitle, EuiText } from import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; -import { LogRateResults as Results } from '../../../../../containers/logs/log_analysis/log_analysis_results'; +import { LogEntryRateResults as Results } from '../../use_log_entry_rate_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { LogEntryRateBarChart } from './bar_chart'; import { getLogEntryRatePartitionedSeries } from '../helpers/data_formatters'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/get_log_entry_rate.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/get_log_entry_rate.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_rate.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/index.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/index.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx similarity index 91% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx index 585a65b9ad1c8..91662c49adace 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_indices_form.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx @@ -25,7 +25,7 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{ onChangeSelectedIndices( indices.map(index => { const checkbox = event.currentTarget; - return index.index === checkbox.id ? { ...index, isSelected: checkbox.checked } : index; + return index.name === checkbox.id ? { ...index, isSelected: checkbox.checked } : index; }) ); }, @@ -35,22 +35,21 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{ const choices = useMemo( () => indices.map(index => { - const validIndex = index.errors.length === 0; const checkbox = ( {index.index}} + key={index.name} + id={index.name} + label={{index.name}} onChange={handleCheckboxChange} - checked={index.isSelected} - disabled={!validIndex} + checked={index.validity === 'valid' && index.isSelected} + disabled={index.validity === 'invalid'} /> ); - return validIndex ? ( + return index.validity === 'valid' ? ( checkbox ) : ( -
    +
    {checkbox}
    ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_timerange_form.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_timerange_form.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/index.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/index.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/index.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/initial_configuration_step.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/initial_configuration_step.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/initial_configuration_step.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/initial_configuration_step.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/create_ml_jobs_button.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/create_ml_jobs_button.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/create_ml_jobs_button.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/create_ml_jobs_button.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/index.ts similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/index.ts rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/index.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/process_step.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/process_step.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/process_step.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/process_step.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/recreate_ml_jobs_button.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/recreate_ml_jobs_button.tsx similarity index 100% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/process_step/recreate_ml_jobs_button.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/process_step/recreate_ml_jobs_button.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/setup_steps.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/setup_steps.tsx similarity index 84% rename from x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/setup_steps.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/setup_steps.tsx index 4643516e73fac..967c69dfae950 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/setup_steps.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/setup_steps.tsx @@ -12,6 +12,10 @@ import { SetupStatus } from '../../../../../common/log_analysis'; import { useAnalysisSetupState } from '../../../../containers/logs/log_analysis/log_analysis_setup_state'; import { InitialConfigurationStep } from './initial_configuration_step'; import { ProcessStep } from './process_step'; +import { + ModuleDescriptor, + ModuleSourceConfiguration, +} from '../../../../containers/logs/log_analysis'; type SetupHandler = ( indices: string[], @@ -19,25 +23,25 @@ type SetupHandler = ( endTime: number | undefined ) => void; -interface AnalysisSetupStepsProps { - availableIndices: string[]; +interface LogEntryRateSetupStepsProps { cleanupAndSetup: SetupHandler; errorMessages: string[]; setup: SetupHandler; setupStatus: SetupStatus; - timestampField: string; viewResults: () => void; + moduleDescriptor: ModuleDescriptor; + sourceConfiguration: ModuleSourceConfiguration; } -export const AnalysisSetupSteps: React.FunctionComponent = ({ - availableIndices, +export const LogEntryRateSetupSteps = ({ cleanupAndSetup: cleanupAndSetupModule, errorMessages, setup: setupModule, setupStatus, - timestampField, viewResults, -}: AnalysisSetupStepsProps) => { + moduleDescriptor, + sourceConfiguration, +}: LogEntryRateSetupStepsProps) => { const { setup, cleanupAndSetup, @@ -50,10 +54,10 @@ export const AnalysisSetupSteps: React.FunctionComponent { + const sourceConfiguration: ModuleSourceConfiguration = useMemo( + () => ({ + indices: indexPattern.split(','), + sourceId, + spaceId, + timestampField, + }), + [indexPattern] + ); + + return useLogAnalysisModule({ + moduleDescriptor: logEntryRateModule, + sourceConfiguration, + }); +}; + +export const [LogEntryRateModuleProvider, useLogEntryRateModuleContext] = createContainer( + useLogEntryRateModule +); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts similarity index 58% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts index 81a80fb565a4b..de2b873001cce 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results.ts @@ -4,75 +4,77 @@ * you may not use this file except in compliance with the Elastic License. */ -import createContainer from 'constate'; -import { useMemo, useEffect } from 'react'; +import { useMemo, useState } from 'react'; -import { useLogEntryRate } from './log_entry_rate'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; +import { + GetLogEntryRateSuccessResponsePayload, + LogEntryRateHistogramBucket, + LogEntryRatePartition, +} from '../../../../common/http_api/log_analysis'; +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { callGetLogEntryRateAPI } from './service_calls/get_log_entry_rate'; -type PartitionBucket = { +type PartitionBucket = LogEntryRatePartition & { startTime: number; -} & GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets'][0]['partitions'][0]; +}; type PartitionRecord = Record< string, { buckets: PartitionBucket[]; topAnomalyScore: number; totalNumberOfLogEntries: number } >; -export interface LogRateResults { +export interface LogEntryRateResults { bucketDuration: number; totalNumberOfLogEntries: number; - histogramBuckets: GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets']; + histogramBuckets: LogEntryRateHistogramBucket[]; partitionBuckets: PartitionRecord; } -export const useLogAnalysisResults = ({ +export const useLogEntryRateResults = ({ sourceId, startTime, endTime, bucketDuration = 15 * 60 * 1000, - lastRequestTime, }: { sourceId: string; startTime: number; endTime: number; - bucketDuration?: number; - lastRequestTime: number; + bucketDuration: number; }) => { - const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ - sourceId, - startTime, - endTime, - bucketDuration, - }); - - const isLoading = useMemo(() => isLoadingLogEntryRate, [isLoadingLogEntryRate]); + const [logEntryRate, setLogEntryRate] = useState(null); - useEffect(() => { - getLogEntryRate(); - }, [sourceId, startTime, endTime, bucketDuration, lastRequestTime]); + const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await callGetLogEntryRateAPI(sourceId, startTime, endTime, bucketDuration); + }, + onResolve: ({ data }) => { + setLogEntryRate({ + bucketDuration: data.bucketDuration, + totalNumberOfLogEntries: data.totalNumberOfLogEntries, + histogramBuckets: data.histogramBuckets, + partitionBuckets: formatLogEntryRateResultsByPartition(data), + }); + }, + onReject: () => { + setLogEntryRate(null); + }, + }, + [sourceId, startTime, endTime, bucketDuration] + ); - const logRateResults: LogRateResults | null = useMemo(() => { - if (logEntryRate) { - return { - bucketDuration: logEntryRate.bucketDuration, - totalNumberOfLogEntries: logEntryRate.totalNumberOfLogEntries, - histogramBuckets: logEntryRate.histogramBuckets, - partitionBuckets: formatLogEntryRateResultsByPartition(logEntryRate), - }; - } else { - return null; - } - }, [logEntryRate]); + const isLoading = useMemo(() => getLogEntryRateRequest.state === 'pending', [ + getLogEntryRateRequest.state, + ]); return { + getLogEntryRate, isLoading, - logRateResults, + logEntryRate, }; }; -export const LogAnalysisResults = createContainer(useLogAnalysisResults); - const formatLogEntryRateResultsByPartition = ( results: GetLogEntryRateSuccessResponsePayload['data'] ): PartitionRecord => { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx similarity index 96% rename from x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx rename to x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx index 19fb7f238fc04..017be6be49e16 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect } from 'react'; -import * as rt from 'io-ts'; -import { identity, constant } from 'fp-ts/lib/function'; import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; +import { useEffect } from 'react'; + import { useUrlState } from '../../../utils/use_url_state'; const autoRefreshRT = rt.union([ diff --git a/x-pack/legacy/plugins/infra/server/infra_server.ts b/x-pack/legacy/plugins/infra/server/infra_server.ts index 845e54e18c7c5..e0c8f607daa93 100644 --- a/x-pack/legacy/plugins/infra/server/infra_server.ts +++ b/x-pack/legacy/plugins/infra/server/infra_server.ts @@ -12,8 +12,8 @@ import { createSourceStatusResolvers } from './graphql/source_status'; import { createSourcesResolvers } from './graphql/sources'; import { InfraBackendLibs } from './lib/infra_types'; import { - initLogAnalysisGetLogEntryRateRoute, - initIndexPatternsValidateRoute, + initGetLogEntryRateRoute, + initValidateLogAnalysisIndicesRoute, } from './routes/log_analysis'; import { initMetricExplorerRoute } from './routes/metrics_explorer'; import { initMetadataRoute } from './routes/metadata'; @@ -33,10 +33,10 @@ export const initInfraServer = (libs: InfraBackendLibs) => { libs.framework.registerGraphQLEndpoint('/graphql', schema); initIpToHostName(libs); - initLogAnalysisGetLogEntryRateRoute(libs); + initGetLogEntryRateRoute(libs); initSnapshotRoute(libs); initNodeDetailsRoute(libs); - initIndexPatternsValidateRoute(libs); + initValidateLogAnalysisIndicesRoute(libs); initMetricExplorerRoute(libs); initMetadataRoute(libs); }; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 625607c098028..e88736b08b95b 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -29,12 +29,12 @@ export interface InfraServerPluginDeps { export interface CallWithRequestParams extends GenericParams { max_concurrent_shard_requests?: number; name?: string; - index?: string; + index?: string | string[]; ignore_unavailable?: boolean; allow_no_indices?: boolean; size?: number; terminate_after?: number; - fields?: string; + fields?: string | string[]; } export type InfraResponse = Lifecycle.ReturnValue; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts index 7364d167efe47..378e32cb3582c 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/index.ts @@ -5,4 +5,4 @@ */ export * from './results'; -export * from './index_patterns'; +export * from './validation'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts index 973080c880e6d..02866e797e305 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts @@ -22,10 +22,7 @@ import { NoLogRateResultsIndexError } from '../../../lib/log_analysis'; const anyObject = schema.object({}, { allowUnknowns: true }); -export const initLogAnalysisGetLogEntryRateRoute = ({ - framework, - logAnalysis, -}: InfraBackendLibs) => { +export const initGetLogEntryRateRoute = ({ framework, logAnalysis }: InfraBackendLibs) => { framework.registerRoute( { method: 'post', diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts similarity index 89% rename from x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts rename to x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts index a85e119e7318a..727faca69298e 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './validate'; +export * from './indices'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts similarity index 77% rename from x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts rename to x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts index 1f64da1859b5f..ba143a597b66d 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/index_patterns/validate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/validation/indices.ts @@ -11,7 +11,7 @@ import { identity } from 'fp-ts/lib/function'; import { schema } from '@kbn/config-schema'; import { InfraBackendLibs } from '../../../lib/infra_types'; import { - LOG_ANALYSIS_VALIDATION_INDICES_PATH, + LOG_ANALYSIS_VALIDATE_INDICES_PATH, validationIndicesRequestPayloadRT, validationIndicesResponsePayloadRT, ValidationIndicesError, @@ -19,14 +19,13 @@ import { import { throwErrors } from '../../../../common/runtime_types'; -const partitionField = 'event.dataset'; const escapeHatch = schema.object({}, { allowUnknowns: true }); -export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) => { +export const initValidateLogAnalysisIndicesRoute = ({ framework }: InfraBackendLibs) => { framework.registerRoute( { method: 'post', - path: LOG_ANALYSIS_VALIDATION_INDICES_PATH, + path: LOG_ANALYSIS_VALIDATE_INDICES_PATH, validate: { body: escapeHatch }, }, async (requestContext, request, response) => { @@ -36,7 +35,7 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) fold(throwErrors(Boom.badRequest), identity) ); - const { timestampField, indices } = payload.data; + const { fields, indices } = payload.data; const errors: ValidationIndicesError[] = []; // Query each pattern individually, to map correctly the errors @@ -44,7 +43,7 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) indices.map(async index => { const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', { index, - fields: `${timestampField},${partitionField}`, + fields: fields.map(field => field.name), }); if (fieldCaps.indices.length === 0) { @@ -55,32 +54,30 @@ export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) return; } - ([ - [timestampField, 'date'], - [partitionField, 'keyword'], - ] as const).forEach(([field, fieldType]) => { - const fieldMetadata = fieldCaps.fields[field]; + fields.forEach(({ name: fieldName, validTypes }) => { + const fieldMetadata = fieldCaps.fields[fieldName]; if (fieldMetadata === undefined) { errors.push({ error: 'FIELD_NOT_FOUND', index, - field, + field: fieldName, }); } else { const fieldTypes = Object.keys(fieldMetadata); - if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) { + if (!fieldTypes.every(fieldType => validTypes.includes(fieldType))) { errors.push({ error: `FIELD_NOT_VALID`, index, - field, + field: fieldName, }); } } }); }) ); + return response.ok({ body: validationIndicesResponsePayloadRT.encode({ data: { errors } }), }); From 48d897e6e7127c28b92be1fc210c06d268a2d982 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Tue, 10 Dec 2019 12:01:48 -0700 Subject: [PATCH 41/56] [SIEM][Detection Engine] Adds the default name space to the end of the signals index ## Summary One liner to add the `default` to the end of the siem signals index for people to play with it. ### 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)~~ --- .../siem/public/pages/detection_engine/signals/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx index ca178db9cd97f..74b7b9349c2cb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/signals/index.tsx @@ -64,7 +64,7 @@ export const SignalsTable = React.memo(() => { {({ to, from, setQuery, deleteQuery, isInitializing }) => ( Date: Tue, 10 Dec 2019 12:55:41 -0700 Subject: [PATCH 42/56] [Telemetry/Pulse] Updates advanced settings text for usage data (#52657) * [Telemetry/Pulse] Updates advanced settings text for usage data --- .../__snapshots__/telemetry_form.test.js.snap | 31 +++++++++++-------- .../public/components/telemetry_form.js | 28 +++++++++++------ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap index a7f8d72e016f8..079a43e77616d 100644 --- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/telemetry_form.test.js.snap @@ -38,7 +38,24 @@ exports[`TelemetryForm renders as expected when allows to change optIn status 1` "defVal": true, "description":

    - Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic. + + + , + } + } + />

    -

    - - - -

    , "type": "boolean", "value": false, diff --git a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index d4bbe1029b40d..f6012a271cde5 100644 --- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -29,7 +29,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { PRIVACY_STATEMENT_URL } from '../../common/constants'; import { OptInExampleFlyout } from './opt_in_details_component'; import { Field } from 'ui/management'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -162,7 +162,23 @@ export class TelemetryForm extends Component { renderDescription = () => ( -

    {getConfigTelemetryDesc()}

    +

    + + + + ) + }} + /> +

    -

    - - - -

    ) From 6e476e845d38018a1f069edadd81065ac5490dd0 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 10 Dec 2019 12:30:11 -0800 Subject: [PATCH 43/56] [DOCS] Updtes description of elasticsearch.requestHeadersWhitelist (#52675) --- docs/setup/settings.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 39c87d97af4ba..5cda7b2b214f0 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -65,6 +65,8 @@ connects to this Kibana instance. `elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side headers, set this value to [] (an empty list). +Removing the `authorization` header from being whitelisted means that you cannot +use <> in Kibana. `elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or Elasticsearch. This value must be a positive From 0eb4c18fe09c3f01bee3ec16206738a3a53f78ae Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 10 Dec 2019 22:05:19 +0000 Subject: [PATCH 44/56] feat(NA): add trap for SIGINT in the git precommit hook (#52662) --- src/dev/register_git_hook/register_git_hook.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index a61922078e687..31136cab0adae 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -58,6 +58,15 @@ function getKbnPrecommitGitHookScript(rootPath, nodeHome, platform) { set -euo pipefail + # Make it possible to terminate pre commit hook + # using ctrl-c so nothing else would happen or be + # sent to the output. + # + # The correct exit code on that situation + # according the linux documentation project is 130 + # https://www.tldp.org/LDP/abs/html/exitcodes.html + trap "exit 130" SIGINT + has_node() { command -v node >/dev/null 2>&1 } From 79fc07c0c3505a0d45ac00aca7573cb21ef3c531 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Tue, 10 Dec 2019 18:02:03 -0500 Subject: [PATCH 45/56] Add top level examples folder and command to run, `--run-examples`. (#52027) * Add top level examples folder and command to run, `--run-examples`. * Add comment explaining reason --run-examples flag turns off base path. --- .ci/packer_cache.sh | 1 + examples/README.md | 8 +++ examples/demo_search/README.md | 8 +++ .../demo_search/common/index.ts | 5 +- .../demo_search/kibana.json | 0 .../demo_search/package.json | 2 +- .../public/demo_search_strategy.ts | 4 +- .../demo_search/public/index.ts | 0 .../demo_search/public/plugin.ts | 6 +- .../server/demo_search_strategy.ts | 2 +- .../demo_search/server/index.ts | 0 .../demo_search/server/plugin.ts | 2 +- .../demo_search/tsconfig.json | 4 +- examples/search_explorer/README.md | 8 +++ .../search_explorer/kibana.json | 0 .../search_explorer/package.json | 2 +- .../search_explorer/public/application.tsx | 2 +- .../search_explorer/public/demo_strategy.tsx | 2 +- .../search_explorer/public/do_search.tsx | 5 +- .../search_explorer/public/documentation.tsx | 0 .../search_explorer/public/es_strategy.tsx | 10 ++-- .../search_explorer/public/guide_section.tsx | 0 .../search_explorer/public/index.ts | 0 .../search_explorer/public/page.tsx | 0 .../search_explorer/public/plugin.tsx | 2 +- .../search_explorer/public/search_api.tsx | 16 +++--- .../search_explorer/tsconfig.json | 4 +- package.json | 1 + packages/kbn-pm/dist/index.js | 1 + packages/kbn-pm/src/config.ts | 1 + renovate.json5 | 1 + scripts/functional_tests.js | 1 + src/cli/serve/serve.js | 15 ++++- src/dev/renovate/package_globs.ts | 1 + src/dev/typescript/projects.ts | 3 + test/examples/README.md | 23 ++++++++ test/examples/config.js | 55 +++++++++++++++++++ .../search/demo_data.ts | 0 .../search/es_search.ts | 0 .../test_suites => examples}/search/index.ts | 0 test/plugin_functional/config.js | 1 - 41 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/demo_search/README.md rename {test/plugin_functional/plugins => examples}/demo_search/common/index.ts (90%) rename {test/plugin_functional/plugins => examples}/demo_search/kibana.json (100%) rename {test/plugin_functional/plugins => examples}/demo_search/package.json (88%) rename {test/plugin_functional/plugins => examples}/demo_search/public/demo_search_strategy.ts (96%) rename {test/plugin_functional/plugins => examples}/demo_search/public/index.ts (100%) rename {test/plugin_functional/plugins => examples}/demo_search/public/plugin.ts (92%) rename {test/plugin_functional/plugins => examples}/demo_search/server/demo_search_strategy.ts (94%) rename {test/plugin_functional/plugins => examples}/demo_search/server/index.ts (100%) rename {test/plugin_functional/plugins => examples}/demo_search/server/plugin.ts (97%) rename {test/plugin_functional/plugins => examples}/demo_search/tsconfig.json (75%) create mode 100644 examples/search_explorer/README.md rename {test/plugin_functional/plugins => examples}/search_explorer/kibana.json (100%) rename {test/plugin_functional/plugins => examples}/search_explorer/package.json (87%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/application.tsx (97%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/demo_strategy.tsx (98%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/do_search.tsx (97%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/documentation.tsx (100%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/es_strategy.tsx (87%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/guide_section.tsx (100%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/index.ts (100%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/page.tsx (100%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/plugin.tsx (94%) rename {test/plugin_functional/plugins => examples}/search_explorer/public/search_api.tsx (70%) rename {test/plugin_functional/plugins => examples}/search_explorer/tsconfig.json (73%) create mode 100644 test/examples/README.md create mode 100644 test/examples/config.js rename test/{plugin_functional/test_suites => examples}/search/demo_data.ts (100%) rename test/{plugin_functional/test_suites => examples}/search/es_search.ts (100%) rename test/{plugin_functional/test_suites => examples}/search/index.ts (100%) diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index b697f22c009d1..ab68a60dcfc27 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -44,6 +44,7 @@ tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ x-pack/legacy/plugins/*/node_modules \ x-pack/legacy/plugins/reporting/.chromium \ test/plugin_functional/plugins/*/node_modules \ + examples/*/node_modules \ .es \ .chromedriver \ .geckodriver; diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000000..7cade0b35f820 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +## Example plugins + +This folder contains example plugins. To run the plugins in this folder, use the `--run-examples` flag, via + +``` +yarn start --run-examples +``` + diff --git a/examples/demo_search/README.md b/examples/demo_search/README.md new file mode 100644 index 0000000000000..f0b461e3287b4 --- /dev/null +++ b/examples/demo_search/README.md @@ -0,0 +1,8 @@ +## Demo search strategy + +This example registers a custom search strategy that simply takes a name string in the request and returns the +string `Hello {name}` + +To see the demo search strategy in action, navigate to the `Search explorer` app. + +To run these examples, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/test/plugin_functional/plugins/demo_search/common/index.ts b/examples/demo_search/common/index.ts similarity index 90% rename from test/plugin_functional/plugins/demo_search/common/index.ts rename to examples/demo_search/common/index.ts index 9254412ece291..6587ee96ef61b 100644 --- a/test/plugin_functional/plugins/demo_search/common/index.ts +++ b/examples/demo_search/common/index.ts @@ -17,10 +17,7 @@ * under the License. */ -import { - IKibanaSearchRequest, - IKibanaSearchResponse, -} from '../../../../../src/plugins/data/public'; +import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public'; export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; diff --git a/test/plugin_functional/plugins/demo_search/kibana.json b/examples/demo_search/kibana.json similarity index 100% rename from test/plugin_functional/plugins/demo_search/kibana.json rename to examples/demo_search/kibana.json diff --git a/test/plugin_functional/plugins/demo_search/package.json b/examples/demo_search/package.json similarity index 88% rename from test/plugin_functional/plugins/demo_search/package.json rename to examples/demo_search/package.json index 1f4fa1421906a..404002a50e710 100644 --- a/test/plugin_functional/plugins/demo_search/package.json +++ b/examples/demo_search/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "scripts": { - "kbn": "node ../../../../scripts/kbn.js", + "kbn": "node ../../scripts/kbn.js", "build": "rm -rf './target' && tsc" }, "devDependencies": { diff --git a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts similarity index 96% rename from test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts rename to examples/demo_search/public/demo_search_strategy.ts index 298eaaaf420e0..d2854151e14c8 100644 --- a/test/plugin_functional/plugins/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -22,8 +22,8 @@ import { ISearchContext, SYNC_SEARCH_STRATEGY, ISearchGeneric, -} from '../../../../../src/plugins/data/public'; -import { TSearchStrategyProvider, ISearchStrategy } from '../../../../../src/plugins/data/public'; +} from '../../../src/plugins/data/public'; +import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; diff --git a/test/plugin_functional/plugins/demo_search/public/index.ts b/examples/demo_search/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/public/index.ts rename to examples/demo_search/public/index.ts diff --git a/test/plugin_functional/plugins/demo_search/public/plugin.ts b/examples/demo_search/public/plugin.ts similarity index 92% rename from test/plugin_functional/plugins/demo_search/public/plugin.ts rename to examples/demo_search/public/plugin.ts index 37f8d3955708a..81ba585b99190 100644 --- a/test/plugin_functional/plugins/demo_search/public/plugin.ts +++ b/examples/demo_search/public/plugin.ts @@ -17,8 +17,8 @@ * under the License. */ -import { DataPublicPluginSetup } from '../../../../../src/plugins/data/public'; -import { Plugin, CoreSetup, PluginInitializerContext } from '../../../../../src/core/public'; +import { DataPublicPluginSetup } from '../../../src/plugins/data/public'; +import { Plugin, CoreSetup, PluginInitializerContext } from '../../../src/core/public'; import { DEMO_SEARCH_STRATEGY } from '../common'; import { demoClientSearchStrategyProvider } from './demo_search_strategy'; import { IDemoRequest, IDemoResponse } from '../common'; @@ -36,7 +36,7 @@ interface DemoDataSearchSetupDependencies { * If the caller does not pass in the right `request` shape, typescript will * complain. The caller will also get a typed response. */ -declare module '../../../../../src/plugins/data/public' { +declare module '../../../src/plugins/data/public' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; } diff --git a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts similarity index 94% rename from test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts rename to examples/demo_search/server/demo_search_strategy.ts index d3f2360add6c0..5b0883be1fc51 100644 --- a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TSearchStrategyProvider } from 'src/plugins/data/server'; +import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { diff --git a/test/plugin_functional/plugins/demo_search/server/index.ts b/examples/demo_search/server/index.ts similarity index 100% rename from test/plugin_functional/plugins/demo_search/server/index.ts rename to examples/demo_search/server/index.ts diff --git a/test/plugin_functional/plugins/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts similarity index 97% rename from test/plugin_functional/plugins/demo_search/server/plugin.ts rename to examples/demo_search/server/plugin.ts index c6628e7c76820..23c82225563c8 100644 --- a/test/plugin_functional/plugins/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -35,7 +35,7 @@ interface IDemoSearchExplorerDeps { * If the caller does not pass in the right `request` shape, typescript will * complain. The caller will also get a typed response. */ -declare module '../../../../../src/plugins/data/server' { +declare module '../../../src/plugins/data/server' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; } diff --git a/test/plugin_functional/plugins/demo_search/tsconfig.json b/examples/demo_search/tsconfig.json similarity index 75% rename from test/plugin_functional/plugins/demo_search/tsconfig.json rename to examples/demo_search/tsconfig.json index 304ffdc0a299d..7fa03739119b4 100644 --- a/test/plugin_functional/plugins/demo_search/tsconfig.json +++ b/examples/demo_search/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true @@ -10,7 +10,7 @@ "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", - "../../../../typings/**/*" + "../../typings/**/*" ], "exclude": [] } diff --git a/examples/search_explorer/README.md b/examples/search_explorer/README.md new file mode 100644 index 0000000000000..0e5a48cf22dc1 --- /dev/null +++ b/examples/search_explorer/README.md @@ -0,0 +1,8 @@ +## Search explorer + +This example search explorer app shows how to use different search strategies in order to retrieve data. + +One demo uses the built in elasticsearch search strategy, and runs a search against data in elasticsearch. The +other demo uses the custom demo search strategy, a custom search strategy registerd inside the [demo_search plugin](../demo_search). + +To run this example, use the command `yarn start --run-examples`. \ No newline at end of file diff --git a/test/plugin_functional/plugins/search_explorer/kibana.json b/examples/search_explorer/kibana.json similarity index 100% rename from test/plugin_functional/plugins/search_explorer/kibana.json rename to examples/search_explorer/kibana.json diff --git a/test/plugin_functional/plugins/search_explorer/package.json b/examples/search_explorer/package.json similarity index 87% rename from test/plugin_functional/plugins/search_explorer/package.json rename to examples/search_explorer/package.json index 9a5e0e83a2207..62d0127c30cc6 100644 --- a/test/plugin_functional/plugins/search_explorer/package.json +++ b/examples/search_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "scripts": { - "kbn": "node ../../../../scripts/kbn.js", + "kbn": "node ../../scripts/kbn.js", "build": "rm -rf './target' && tsc" }, "devDependencies": { diff --git a/test/plugin_functional/plugins/search_explorer/public/application.tsx b/examples/search_explorer/public/application.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/application.tsx rename to examples/search_explorer/public/application.tsx index 4762209a548c1..801a3c615ac61 100644 --- a/test/plugin_functional/plugins/search_explorer/public/application.tsx +++ b/examples/search_explorer/public/application.tsx @@ -28,7 +28,7 @@ import { EuiSideNav, } from '@elastic/eui'; -import { AppMountContext, AppMountParameters } from '../../../../../src/core/public'; +import { AppMountContext, AppMountParameters } from '../../../src/core/public'; import { EsSearchTest } from './es_strategy'; import { Page } from './page'; import { DemoStrategy } from './demo_strategy'; diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/examples/search_explorer/public/demo_strategy.tsx similarity index 98% rename from test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx rename to examples/search_explorer/public/demo_strategy.tsx index 8a0dd31e3595f..7c6c21d2b7aed 100644 --- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx +++ b/examples/search_explorer/public/demo_strategy.tsx @@ -25,7 +25,7 @@ import { EuiFlexGroup, EuiFieldText, } from '@elastic/eui'; -import { ISearchGeneric } from '../../../../../src/plugins/data/public'; +import { ISearchGeneric } from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; diff --git a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx b/examples/search_explorer/public/do_search.tsx similarity index 97% rename from test/plugin_functional/plugins/search_explorer/public/do_search.tsx rename to examples/search_explorer/public/do_search.tsx index e039e4ff3f63f..f279b9fcd6e23 100644 --- a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx +++ b/examples/search_explorer/public/do_search.tsx @@ -21,10 +21,7 @@ import React from 'react'; import { EuiButton, EuiCodeBlock, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; import { EuiProgress } from '@elastic/eui'; import { Observable } from 'rxjs'; -import { - IKibanaSearchResponse, - IKibanaSearchRequest, -} from '../../../../../src/plugins/data/public'; +import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../../src/plugins/data/public'; interface Props { request: IKibanaSearchRequest; diff --git a/test/plugin_functional/plugins/search_explorer/public/documentation.tsx b/examples/search_explorer/public/documentation.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/documentation.tsx rename to examples/search_explorer/public/documentation.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx b/examples/search_explorer/public/es_strategy.tsx similarity index 87% rename from test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx rename to examples/search_explorer/public/es_strategy.tsx index d35c67191a1f8..e26c11a646669 100644 --- a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx +++ b/examples/search_explorer/public/es_strategy.tsx @@ -29,19 +29,19 @@ import { ISearchGeneric, IEsSearchResponse, IEsSearchRequest, -} from '../../../../../src/plugins/data/public'; +} from '../../../src/plugins/data/public'; import { DoSearch } from './do_search'; import { GuideSection } from './guide_section'; // @ts-ignore -import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_service'; +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_service'; // @ts-ignore -import serverStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/es_search/es_search_strategy'; +import serverStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/es_search/es_search_strategy'; // @ts-ignore -import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_service'; +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_service'; // @ts-ignore -import publicStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/es_search/es_search_strategy'; +import publicStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/es_search/es_search_strategy'; interface Props { search: ISearchGeneric; diff --git a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx b/examples/search_explorer/public/guide_section.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/guide_section.tsx rename to examples/search_explorer/public/guide_section.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/index.ts b/examples/search_explorer/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/index.ts rename to examples/search_explorer/public/index.ts diff --git a/test/plugin_functional/plugins/search_explorer/public/page.tsx b/examples/search_explorer/public/page.tsx similarity index 100% rename from test/plugin_functional/plugins/search_explorer/public/page.tsx rename to examples/search_explorer/public/page.tsx diff --git a/test/plugin_functional/plugins/search_explorer/public/plugin.tsx b/examples/search_explorer/public/plugin.tsx similarity index 94% rename from test/plugin_functional/plugins/search_explorer/public/plugin.tsx rename to examples/search_explorer/public/plugin.tsx index cbe1073aa186b..a7a6fd11341a4 100644 --- a/test/plugin_functional/plugins/search_explorer/public/plugin.tsx +++ b/examples/search_explorer/public/plugin.tsx @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup } from 'kibana/public'; -import { ISearchAppMountContext } from '../../../../../src/plugins/data/public'; +import { ISearchAppMountContext } from '../../../src/plugins/data/public'; declare module 'kibana/public' { interface AppMountContext { diff --git a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx b/examples/search_explorer/public/search_api.tsx similarity index 70% rename from test/plugin_functional/plugins/search_explorer/public/search_api.tsx rename to examples/search_explorer/public/search_api.tsx index 8ec6225d1f172..fc68571e4ef68 100644 --- a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx +++ b/examples/search_explorer/public/search_api.tsx @@ -20,22 +20,22 @@ import React from 'react'; import { GuideSection } from './guide_section'; // @ts-ignore -import publicSetupContract from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_setup'; +import publicSetupContract from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_setup'; // @ts-ignore -import publicSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search_strategy'; +import publicSearchStrategy from '!!raw-loader!./../../../src/plugins/data/public/search/i_search_strategy'; // @ts-ignore -import publicSearch from '!!raw-loader!./../../../../../src/plugins/data/public/search/i_search'; +import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search'; // @ts-ignore -import publicPlugin from '!!raw-loader!./../../../../../src/plugins/data/public/search/search_service'; +import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service'; // @ts-ignore -import serverSetupContract from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_setup'; +import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup'; // @ts-ignore -import serverSearchStrategy from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search_strategy'; +import serverSearchStrategy from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_strategy'; // @ts-ignore -import serverSearch from '!!raw-loader!./../../../../../src/plugins/data/server/search/i_search'; +import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search'; // @ts-ignore -import serverPlugin from '!!raw-loader!./../../../../../src/plugins/data/server/search/search_service'; +import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service'; export const SearchApiPage = () => ( new Project(resolve(REPO_ROOT, path))), + ...glob + .sync('examples/*/tsconfig.json', { cwd: REPO_ROOT }) + .map(path => new Project(resolve(REPO_ROOT, path))), ...glob .sync('test/plugin_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) .map(path => new Project(resolve(REPO_ROOT, path))), diff --git a/test/examples/README.md b/test/examples/README.md new file mode 100644 index 0000000000000..44656f949bc72 --- /dev/null +++ b/test/examples/README.md @@ -0,0 +1,23 @@ +# Example plugin functional tests + +This folder contains functional tests for the example plugins. + +## Run the test + +To run these tests during development you can use the following commands: + +``` +# Start the test server (can continue running) +node scripts/functional_tests_server.js --config test/examples/config.js +# Start a test run +node scripts/functional_test_runner.js --config test/examples/config.js +``` + +## Run Kibana with a test plugin + +In case you want to start Kibana with the example plugins, you can just run: + +``` +yarn start --run-examples +``` + diff --git a/test/examples/config.js b/test/examples/config.js new file mode 100644 index 0000000000000..b954390dc54ad --- /dev/null +++ b/test/examples/config.js @@ -0,0 +1,55 @@ +/* + * 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 path from 'path'; +import { services } from '../plugin_functional/services'; + +export default async function ({ readConfigFile }) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config')); + + return { + testFiles: [ + require.resolve('./search'), + ], + services: { + ...functionalConfig.get('services'), + ...services, + }, + pageObjects: functionalConfig.get('pageObjects'), + servers: functionalConfig.get('servers'), + esTestCluster: functionalConfig.get('esTestCluster'), + apps: functionalConfig.get('apps'), + esArchiver: { + directory: path.resolve(__dirname, '../es_archives') + }, + screenshots: functionalConfig.get('screenshots'), + junit: { + reportName: 'Example plugin functional tests', + }, + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + '--run-examples', + // Required to run examples + '--env.name=development', + ], + }, + }; +} diff --git a/test/plugin_functional/test_suites/search/demo_data.ts b/test/examples/search/demo_data.ts similarity index 100% rename from test/plugin_functional/test_suites/search/demo_data.ts rename to test/examples/search/demo_data.ts diff --git a/test/plugin_functional/test_suites/search/es_search.ts b/test/examples/search/es_search.ts similarity index 100% rename from test/plugin_functional/test_suites/search/es_search.ts rename to test/examples/search/es_search.ts diff --git a/test/plugin_functional/test_suites/search/index.ts b/test/examples/search/index.ts similarity index 100% rename from test/plugin_functional/test_suites/search/index.ts rename to test/examples/search/index.ts diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index a6316c607a7c7..d8ce12d1fc612 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -33,7 +33,6 @@ export default async function ({ readConfigFile }) { require.resolve('./test_suites/app_plugins'), require.resolve('./test_suites/custom_visualizations'), require.resolve('./test_suites/panel_actions'), - require.resolve('./test_suites/search'), /** * @todo Work on re-enabling this test suite after this is merged. These tests pass From 3e1915d287bb4c78239b1ef71849172009899317 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 10 Dec 2019 21:07:57 -0700 Subject: [PATCH 46/56] fix newlines in kbn-analytics build script --- packages/kbn-analytics/scripts/build.js | 190 ++++++++++++------------ packages/kbn-i18n/scripts/build.js | 2 +- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/packages/kbn-analytics/scripts/build.js b/packages/kbn-analytics/scripts/build.js index 3736ab15260fa..b7fbe629246ec 100644 --- a/packages/kbn-analytics/scripts/build.js +++ b/packages/kbn-analytics/scripts/build.js @@ -1,95 +1,95 @@ -/* - * 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. - */ - -const { resolve } = require('path'); - -const del = require('del'); -const supportsColor = require('supports-color'); -const { run, withProcRunner } = require('@kbn/dev-utils'); - -const ROOT_DIR = resolve(__dirname, '..'); -const BUILD_DIR = resolve(ROOT_DIR, 'target'); - -const padRight = (width, str) => - str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; - -run( - async ({ log, flags }) => { - await withProcRunner(log, async proc => { - log.info('Deleting old output'); - await del(BUILD_DIR); - - const cwd = ROOT_DIR; - const env = { ...process.env }; - if (supportsColor.stdout) { - env.FORCE_COLOR = 'true'; - } - - log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); - await Promise.all([ - ...['web', 'node'].map(subTask => - proc.run(padRight(10, `babel:${subTask}`), { - cmd: 'babel', - args: [ - 'src', - '--config-file', - require.resolve('../babel.config.js'), - '--out-dir', - resolve(BUILD_DIR, subTask), - '--extensions', - '.ts,.js,.tsx', - ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), - ], - wait: true, - env: { - ...env, - BABEL_ENV: subTask, - }, - cwd, - }) - ), - - proc.run(padRight(10, 'tsc'), { - cmd: 'tsc', - args: [ - '--emitDeclarationOnly', - ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), - ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), - ], - wait: true, - env, - cwd, - }), - ]); - - log.success('Complete'); - }); - }, - { - description: 'Simple build tool for @kbn/analytics package', - flags: { - boolean: ['watch', 'source-maps'], - help: ` - --watch Run in watch mode - --source-maps Include sourcemaps - `, - }, - } -); +/* + * 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. + */ + +const { resolve } = require('path'); + +const del = require('del'); +const supportsColor = require('supports-color'); +const { run, withProcRunner } = require('@kbn/dev-utils'); + +const ROOT_DIR = resolve(__dirname, '..'); +const BUILD_DIR = resolve(ROOT_DIR, 'target'); + +const padRight = (width, str) => + str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; + +run( + async ({ log, flags }) => { + await withProcRunner(log, async proc => { + log.info('Deleting old output'); + await del(BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); + await Promise.all([ + ...['web', 'node'].map(subTask => + proc.run(padRight(10, `babel:${subTask}`), { + cmd: 'babel', + args: [ + 'src', + '--config-file', + require.resolve('../babel.config.js'), + '--out-dir', + resolve(BUILD_DIR, subTask), + '--extensions', + '.ts,.js,.tsx', + ...(flags.watch ? ['--watch'] : ['--quiet']), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), + ], + wait: true, + env: { + ...env, + BABEL_ENV: subTask, + }, + cwd, + }) + ), + + proc.run(padRight(10, 'tsc'), { + cmd: 'tsc', + args: [ + '--emitDeclarationOnly', + ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []), + ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []), + ], + wait: true, + env, + cwd, + }), + ]); + + log.success('Complete'); + }); + }, + { + description: 'Simple build tool for @kbn/analytics package', + flags: { + boolean: ['watch', 'source-maps'], + help: ` + --watch Run in watch mode + --source-maps Include sourcemaps + `, + }, + } +); diff --git a/packages/kbn-i18n/scripts/build.js b/packages/kbn-i18n/scripts/build.js index f4260d31d80fb..ccdddc87dbc18 100644 --- a/packages/kbn-i18n/scripts/build.js +++ b/packages/kbn-i18n/scripts/build.js @@ -55,7 +55,7 @@ run( '--extensions', '.ts,.js,.tsx', ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), + ...(flags['source-maps'] ? ['--source-maps', 'inline'] : []), ], wait: true, env: { From 1013271c858b4c7c6107be551ada20323f989497 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 11 Dec 2019 10:28:54 +0300 Subject: [PATCH 47/56] [ui/public/utils] Move items into ui/vis (#52615) * [ui/public/utils] Move items into ui/vis * fix PR comments --- .../ui/public/agg_types/buckets/geo_hash.ts | 2 +- src/legacy/ui/public/utils/range.d.ts | 28 -------- .../__snapshots__/number_list.test.tsx.snap | 4 +- .../components/number_list/number_row.tsx | 4 +- .../components/number_list/range.test.ts} | 71 +++++++++---------- .../controls/components/number_list/range.ts} | 61 ++++++++-------- .../components/number_list/utils.test.ts | 4 +- .../controls/components/number_list/utils.ts | 10 +-- .../ui/public/vis/map/convert_to_geojson.js | 3 +- .../map/decode_geo_hash.test.ts} | 19 ++--- .../{utils => vis/map}/decode_geo_hash.ts | 0 src/legacy/ui/public/vis/map/kibana_map.js | 2 +- .../map/zoom_to_precision.ts} | 45 ++++++------ 13 files changed, 108 insertions(+), 145 deletions(-) delete mode 100644 src/legacy/ui/public/utils/range.d.ts rename src/legacy/ui/public/{utils/__tests__/range.js => vis/editors/default/controls/components/number_list/range.test.ts} (66%) rename src/legacy/ui/public/{utils/range.js => vis/editors/default/controls/components/number_list/range.ts} (59%) rename src/legacy/ui/public/{utils/__tests__/decode_geo_hash.test.js => vis/map/decode_geo_hash.test.ts} (75%) rename src/legacy/ui/public/{utils => vis/map}/decode_geo_hash.ts (100%) rename src/legacy/ui/public/{utils/zoom_to_precision.js => vis/map/zoom_to_precision.ts} (52%) diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts index 700f5a048fce2..0acbaf4aa02a2 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts @@ -18,13 +18,13 @@ */ import { i18n } from '@kbn/i18n'; +import { geohashColumns } from 'ui/vis/map/decode_geo_hash'; import chrome from '../../chrome'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { AutoPrecisionParamEditor } from '../../vis/editors/default/controls/auto_precision'; import { UseGeocentroidParamEditor } from '../../vis/editors/default/controls/use_geocentroid'; import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/controls/is_filtered_by_collar'; import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision'; -import { geohashColumns } from '../../utils/decode_geo_hash'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/utils/range.d.ts b/src/legacy/ui/public/utils/range.d.ts deleted file mode 100644 index c484c6f43eebb..0000000000000 --- a/src/legacy/ui/public/utils/range.d.ts +++ /dev/null @@ -1,28 +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. - */ - -export function parseRange(input: string): Range; - -export interface Range { - min: number; - max: number; - minInclusive: boolean; - maxInclusive: boolean; - within(n: number): boolean; -} diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap index ab192e6fd3cbb..4004f8627a898 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap @@ -18,7 +18,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, @@ -45,7 +45,7 @@ exports[`NumberList should be rendered with default set of props 1`] = ` onChange={[Function]} onDelete={[Function]} range={ - Range { + NumberListRange { "max": 10, "maxInclusive": true, "min": 1, diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx index 23e671180e980..777b0a94f0f3d 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx @@ -21,7 +21,7 @@ import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; export interface NumberRowProps { autoFocus: boolean; @@ -29,7 +29,7 @@ export interface NumberRowProps { isInvalid: boolean; labelledbyId: string; model: NumberRowModel; - range: Range; + range: NumberListRange; onBlur(): void; onChange({ id, value }: { id: string; value: string }): void; onDelete(index: string): void; diff --git a/src/legacy/ui/public/utils/__tests__/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts similarity index 66% rename from src/legacy/ui/public/utils/__tests__/range.js rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts index e7947894d3e22..e9090e5b38ef7 100644 --- a/src/legacy/ui/public/utils/__tests__/range.js +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.test.ts @@ -17,32 +17,30 @@ * under the License. */ -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { parseRange } from '../range'; +import { forOwn } from 'lodash'; +import { parseRange } from './range'; -describe('Range parsing utility', function () { - - it('throws an error for inputs that are not formatted properly', function () { - expect(function () { +describe('Range parsing utility', () => { + test('throws an error for inputs that are not formatted properly', () => { + expect(() => { parseRange(''); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('p10202'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('{0,100}'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange('[0,100'); - }).to.throwException(TypeError); + }).toThrowError(TypeError); - expect(function () { + expect(function() { parseRange(')0,100('); - }).to.throwException(TypeError); + }).toThrowError(TypeError); }); const tests = { @@ -51,52 +49,52 @@ describe('Range parsing utility', function () { min: 0, max: 100, minInclusive: true, - maxInclusive: true + maxInclusive: true, }, within: [ [0, true], [0.0000001, true], [1, true], [99.99999, true], - [100, true] - ] + [100, true], + ], }, '(26.3 , 42]': { props: { min: 26.3, max: 42, minInclusive: false, - maxInclusive: true + maxInclusive: true, }, within: [ [26.2999999, false], [26.3000001, true], [30, true], [41, true], - [42, true] - ] + [42, true], + ], }, '(-50,50)': { props: { min: -50, max: 50, minInclusive: false, - maxInclusive: false + maxInclusive: false, }, within: [ [-50, false], [-49.99999, true], [0, true], [49.99999, true], - [50, false] - ] + [50, false], + ], }, '(Infinity, -Infinity)': { props: { min: -Infinity, max: Infinity, minInclusive: false, - maxInclusive: false + maxInclusive: false, }, within: [ [0, true], @@ -105,25 +103,24 @@ describe('Range parsing utility', function () { [-10000000000, true], [-Infinity, false], [Infinity, false], - ] - } + ], + }, }; - _.forOwn(tests, function (spec, str) { - - describe(str, function () { + forOwn(tests, (spec, str: any) => { + // eslint-disable-next-line jest/valid-describe + describe(str, () => { const range = parseRange(str); - it('creation', function () { - expect(range).to.eql(spec.props); + it('creation', () => { + expect(range).toEqual(spec.props); }); - spec.within.forEach(function (tup) { - it('#within(' + tup[0] + ')', function () { - expect(range.within(tup[0])).to.be(tup[1]); + spec.within.forEach((tup: any[]) => { + it('#within(' + tup[0] + ')', () => { + expect(range.within(tup[0])).toBe(tup[1]); }); }); }); - }); }); diff --git a/src/legacy/ui/public/utils/range.js b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts similarity index 59% rename from src/legacy/ui/public/utils/range.js rename to src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts index 54bd1b1903346..da3b7a61aea9d 100644 --- a/src/legacy/ui/public/utils/range.js +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/range.ts @@ -17,8 +17,6 @@ * under the License. */ -import _ from 'lodash'; - /** * Regexp portion that matches our number * @@ -44,41 +42,44 @@ const _RE_NUMBER = '(\\-?(?:\\d+(?:\\.\\d+)?|Infinity))'; * * @type {RegExp} */ -const RANGE_RE = new RegExp('^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$'); +const RANGE_RE = new RegExp( + '^\\s*([\\[|\\(])\\s*' + _RE_NUMBER + '\\s*,\\s*' + _RE_NUMBER + '\\s*([\\]|\\)])\\s*$' +); + +export class NumberListRange { + constructor( + public minInclusive: boolean, + public min: number, + public max: number, + public maxInclusive: boolean + ) {} -export function parseRange(input) { + within(n: number): boolean { + if ((this.min === n && !this.minInclusive) || this.min > n) return false; + if ((this.max === n && !this.maxInclusive) || this.max < n) return false; + + return true; + } +} +export function parseRange(input: string): NumberListRange { const match = String(input).match(RANGE_RE); if (!match) { throw new TypeError('expected input to be in interval notation e.g., (100, 200]'); } - return new Range( - match[1] === '[', - parseFloat(match[2]), - parseFloat(match[3]), - match[4] === ']' - ); -} - -function Range(/* minIncl, min, max, maxIncl */) { - const args = _.toArray(arguments); - if (args[1] > args[2]) args.reverse(); + const args = [match[1] === '[', parseFloat(match[2]), parseFloat(match[3]), match[4] === ']']; - this.minInclusive = args[0]; - this.min = args[1]; - this.max = args[2]; - this.maxInclusive = args[3]; -} - -Range.prototype.within = function (n) { - if (this.min === n && !this.minInclusive) return false; - if (this.min > n) return false; - - if (this.max === n && !this.maxInclusive) return false; - if (this.max < n) return false; - - return true; -}; + if (args[1] > args[2]) { + args.reverse(); + } + const [minInclusive, min, max, maxInclusive] = args; + return new NumberListRange( + minInclusive as boolean, + min as number, + max as number, + maxInclusive as boolean + ); +} diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts index c6772cc108762..89fb5738db379 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts @@ -27,12 +27,12 @@ import { getNextModel, getRange, } from './utils'; -import { Range } from '../../../../../../utils/range'; +import { NumberListRange } from './range'; import { NumberRowModel } from './number_row'; describe('NumberList utils', () => { let modelList: NumberRowModel[]; - let range: Range; + let range: NumberListRange; beforeEach(() => { modelList = [ diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts index 563e8f0a6a9b7..399253f27445c 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts @@ -21,7 +21,7 @@ import { last } from 'lodash'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator } from '@elastic/eui'; -import { parseRange, Range } from '../../../../../../utils/range'; +import { parseRange, NumberListRange } from './range'; import { NumberRowModel } from './number_row'; const EMPTY_STRING = ''; @@ -34,7 +34,7 @@ function parse(value: string) { return isNaN(parsedValue) ? EMPTY_STRING : parsedValue; } -function getRange(range?: string): Range { +function getRange(range?: string): NumberListRange { try { return range ? parseRange(range) : defaultRange; } catch (e) { @@ -42,7 +42,7 @@ function getRange(range?: string): Range { } } -function validateValue(value: number | '', numberRange: Range) { +function validateValue(value: number | '', numberRange: NumberListRange) { const result: { isInvalid: boolean; error?: string } = { isInvalid: false, }; @@ -76,7 +76,7 @@ function validateOrder(list: Array) { return result; } -function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel { +function getNextModel(list: NumberRowModel[], range: NumberListRange): NumberRowModel { const lastValue = last(list).value; let next = Number(lastValue) ? Number(lastValue) + 1 : 1; @@ -104,7 +104,7 @@ function getInitModelList(list: Array): NumberRowModel[] { function getUpdatedModels( numberList: Array, modelList: NumberRowModel[], - numberRange: Range, + numberRange: NumberListRange, invalidOrderModelIndex?: number ): NumberRowModel[] { if (!numberList.length) { diff --git a/src/legacy/ui/public/vis/map/convert_to_geojson.js b/src/legacy/ui/public/vis/map/convert_to_geojson.js index 77896490678ff..14c282b58beda 100644 --- a/src/legacy/ui/public/vis/map/convert_to_geojson.js +++ b/src/legacy/ui/public/vis/map/convert_to_geojson.js @@ -17,10 +17,9 @@ * under the License. */ -import { decodeGeoHash } from 'ui/utils/decode_geo_hash'; +import { decodeGeoHash } from './decode_geo_hash'; import { gridDimensions } from './grid_dimensions'; - export function convertToGeoJson(tabifiedResponse, { geohash, geocentroid, metric }) { let features; diff --git a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts similarity index 75% rename from src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js rename to src/legacy/ui/public/vis/map/decode_geo_hash.test.ts index 1ffe9ca7b4df2..c1ca7e4c80383 100644 --- a/src/legacy/ui/public/utils/__tests__/decode_geo_hash.test.js +++ b/src/legacy/ui/public/vis/map/decode_geo_hash.test.ts @@ -17,27 +17,18 @@ * under the License. */ -import { geohashColumns, decodeGeoHash } from '../decode_geo_hash'; +import { geohashColumns, decodeGeoHash } from './decode_geo_hash'; -test('geohashColumns', function () { +test('geohashColumns', () => { expect(geohashColumns(1)).toBe(8); expect(geohashColumns(2)).toBe(8 * 4); expect(geohashColumns(3)).toBe(8 * 4 * 8); expect(geohashColumns(4)).toBe(8 * 4 * 8 * 4); }); -test('decodeGeoHash', function () { +test('decodeGeoHash', () => { expect(decodeGeoHash('drm3btev3e86')).toEqual({ - latitude: [ - 41.119999922811985, - 41.12000009045005, - 41.12000000663102, - ], - longitude: [ - -71.34000029414892, - -71.3399999588728, - -71.34000012651086, - ], + latitude: [41.119999922811985, 41.12000009045005, 41.12000000663102], + longitude: [-71.34000029414892, -71.3399999588728, -71.34000012651086], }); }); - diff --git a/src/legacy/ui/public/utils/decode_geo_hash.ts b/src/legacy/ui/public/vis/map/decode_geo_hash.ts similarity index 100% rename from src/legacy/ui/public/utils/decode_geo_hash.ts rename to src/legacy/ui/public/vis/map/decode_geo_hash.ts diff --git a/src/legacy/ui/public/vis/map/kibana_map.js b/src/legacy/ui/public/vis/map/kibana_map.js index dc57809b6570f..cb618444af7ce 100644 --- a/src/legacy/ui/public/vis/map/kibana_map.js +++ b/src/legacy/ui/public/vis/map/kibana_map.js @@ -22,7 +22,7 @@ import { createZoomWarningMsg } from './map_messages'; import L from 'leaflet'; import $ from 'jquery'; import _ from 'lodash'; -import { zoomToPrecision } from '../../utils/zoom_to_precision'; +import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; import { ORIGIN } from '../../../../core_plugins/tile_map/common/origin'; diff --git a/src/legacy/ui/public/utils/zoom_to_precision.js b/src/legacy/ui/public/vis/map/zoom_to_precision.ts similarity index 52% rename from src/legacy/ui/public/utils/zoom_to_precision.js rename to src/legacy/ui/public/vis/map/zoom_to_precision.ts index f5c16b640d127..552c509590286 100644 --- a/src/legacy/ui/public/utils/zoom_to_precision.js +++ b/src/legacy/ui/public/vis/map/zoom_to_precision.ts @@ -19,39 +19,42 @@ import { geohashColumns } from './decode_geo_hash'; -const maxPrecision = 12; -/** - * Map Leaflet zoom levels to geohash precision levels. - * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. - */ - - +const defaultMaxPrecision = 12; +const minGeoHashPixels = 16; - -const zoomPrecisionMap = {}; -const minGeohashPixels = 16; - -function calculateZoomToPrecisionMap(maxZoom) { +const calculateZoomToPrecisionMap = (maxZoom: number): Map => { + /** + * Map Leaflet zoom levels to geohash precision levels. + * The size of a geohash column-width on the map should be at least `minGeohashPixels` pixels wide. + */ + const zoomPrecisionMap = new Map(); for (let zoom = 0; zoom <= maxZoom; zoom += 1) { - if (typeof zoomPrecisionMap[zoom] === 'number') { + if (typeof zoomPrecisionMap.get(zoom) === 'number') { continue; } + const worldPixels = 256 * Math.pow(2, zoom); - zoomPrecisionMap[zoom] = 1; - for (let precision = 2; precision <= maxPrecision; precision += 1) { + + zoomPrecisionMap.set(zoom, 1); + + for (let precision = 2; precision <= defaultMaxPrecision; precision += 1) { const columns = geohashColumns(precision); - if ((worldPixels / columns) >= minGeohashPixels) { - zoomPrecisionMap[zoom] = precision; + + if (worldPixels / columns >= minGeoHashPixels) { + zoomPrecisionMap.set(zoom, precision); } else { break; } } } -} + return zoomPrecisionMap; +}; + +export function zoomToPrecision(mapZoom: number, maxPrecision: number, maxZoom: number) { + const zoomPrecisionMap = calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); + const precision = zoomPrecisionMap.get(mapZoom); -export function zoomToPrecision(mapZoom, maxPrecision, maxZoom) { - calculateZoomToPrecisionMap(typeof maxZoom === 'number' ? maxZoom : 21); - return Math.min(zoomPrecisionMap[mapZoom], maxPrecision); + return precision ? Math.min(precision, maxPrecision) : maxPrecision; } From 6a8b2a25c824d1d8eb12178f84a6a16ce92b594b Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 11 Dec 2019 10:30:45 +0300 Subject: [PATCH 48/56] [ui/public/utils] Delete unused base_object & find_by_param (#52500) Closes #51854 --- .../ui/public/state_management/state.js | 21 +++ .../ui/public/utils/__tests__/base_object.js | 57 ------ .../public/utils/__tests__/simple_emitter.js | 175 ------------------ src/legacy/ui/public/utils/base_object.ts | 47 ----- src/legacy/ui/public/utils/find_by_param.ts | 38 ---- src/legacy/ui/public/utils/simple_emitter.js | 4 - .../ui/public/utils/simple_emitter.test.js | 173 +++++++++++++++++ 7 files changed, 194 insertions(+), 321 deletions(-) delete mode 100644 src/legacy/ui/public/utils/__tests__/base_object.js delete mode 100644 src/legacy/ui/public/utils/__tests__/simple_emitter.js delete mode 100644 src/legacy/ui/public/utils/base_object.ts delete mode 100644 src/legacy/ui/public/utils/find_by_param.ts create mode 100644 src/legacy/ui/public/utils/simple_emitter.test.js diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 27186b4249978..e868abb98c852 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -316,6 +316,27 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return this._urlParam; }; + /** + * Returns an object with each property name and value corresponding to the entries in this collection + * excluding fields started from '$', '_' and all methods + * + * @return {object} + */ + State.prototype.toObject = function () { + return _.omit(this, (value, key) => { + return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); + }); + }; + + /** Alias for method 'toObject' + * + * @obsolete Please use 'toObject' method instead + * @return {object} + */ + State.prototype.toJSON = function () { + return this.toObject(); + }; + return State; } diff --git a/src/legacy/ui/public/utils/__tests__/base_object.js b/src/legacy/ui/public/utils/__tests__/base_object.js deleted file mode 100644 index dfc5688c7b2f4..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/base_object.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../../private'; - -import { BaseObject } from '../base_object'; - -describe('Base Object', function () { - beforeEach(ngMock.module('kibana')); - - it('should take an inital set of values', function () { - const baseObject = new BaseObject({ message: 'test' }); - expect(baseObject).to.have.property('message', 'test'); - }); - - it('should serialize attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should not serialize $$attributes to RISON', function () { - const baseObject = new BaseObject(); - baseObject.$$attributes = { foo: 'bar' }; - baseObject.message = 'Testing... 1234'; - const rison = baseObject.toRISON(); - expect(rison).to.equal('(message:\'Testing... 1234\')'); - }); - - it('should serialize attributes for JSON', function () { - const baseObject = new BaseObject(); - baseObject.message = 'Testing... 1234'; - baseObject._private = 'foo'; - baseObject.$private = 'stuff'; - const json = JSON.stringify(baseObject); - expect(json).to.equal('{"message":"Testing... 1234"}'); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/simple_emitter.js b/src/legacy/ui/public/utils/__tests__/simple_emitter.js deleted file mode 100644 index 25224a409f8f4..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/simple_emitter.js +++ /dev/null @@ -1,175 +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 { SimpleEmitter } from '../simple_emitter'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -describe('SimpleEmitter class', function () { - let emitter; - - beforeEach(function () { - emitter = new SimpleEmitter(); - }); - - it('constructs an event emitter', function () { - expect(emitter).to.have.property('on'); - expect(emitter).to.have.property('off'); - expect(emitter).to.have.property('emit'); - expect(emitter).to.have.property('listenerCount'); - expect(emitter).to.have.property('removeAllListeners'); - }); - - describe('#listenerCount', function () { - it('counts all event listeners without any arg', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(1); - emitter.on('b', function () {}); - expect(emitter.listenerCount()).to.be(2); - }); - - it('limits to the event that is passed in', function () { - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(1); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - emitter.on('b', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - expect(emitter.listenerCount('b')).to.be(1); - expect(emitter.listenerCount()).to.be(3); - }); - }); - - describe('#on', function () { - it('registers a handler', function () { - const handler = sinon.stub(); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - - expect(handler.callCount).to.be(0); - emitter.emit('a'); - expect(handler.callCount).to.be(1); - }); - - it('allows multiple event handlers for the same event', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount('a')).to.be(2); - }); - - it('allows the same function to be registered multiple times', function () { - const handler = function () {}; - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(1); - emitter.on('a', handler); - expect(emitter.listenerCount()).to.be(2); - }); - }); - - describe('#off', function () { - it('removes a listener if it was registered', function () { - const handler = sinon.stub(); - expect(emitter.listenerCount()).to.be(0); - emitter.on('a', handler); - expect(emitter.listenerCount('a')).to.be(1); - emitter.off('a', handler); - expect(emitter.listenerCount('a')).to.be(0); - }); - - it('clears all listeners if no handler is passed', function () { - emitter.on('a', function () {}); - emitter.on('a', function () {}); - expect(emitter.listenerCount()).to.be(2); - emitter.off('a'); - expect(emitter.listenerCount()).to.be(0); - }); - - it('does not mind if the listener is not registered', function () { - emitter.off('a', function () {}); - }); - - it('does not mind if the event has no listeners', function () { - emitter.off('a'); - }); - }); - - describe('#emit', function () { - it('calls the handlers in the order they were defined', function () { - let i = 0; - const incr = function () { return ++i; }; - const one = sinon.spy(incr); - const two = sinon.spy(incr); - const three = sinon.spy(incr); - const four = sinon.spy(incr); - - emitter - .on('a', one) - .on('a', two) - .on('a', three) - .on('a', four) - .emit('a'); - - expect(one).to.have.property('callCount', 1); - expect(one.returned(1)).to.be.ok(); - - expect(two).to.have.property('callCount', 1); - expect(two.returned(2)).to.be.ok(); - - expect(three).to.have.property('callCount', 1); - expect(three.returned(3)).to.be.ok(); - - expect(four).to.have.property('callCount', 1); - expect(four.returned(4)).to.be.ok(); - }); - - it('always emits the handlers that were initially registered', function () { - - const destructive = sinon.spy(function () { - emitter.removeAllListeners(); - expect(emitter.listenerCount()).to.be(0); - }); - const stub = sinon.stub(); - - emitter.on('run', destructive).on('run', stub).emit('run'); - - expect(destructive).to.have.property('callCount', 1); - expect(stub).to.have.property('callCount', 1); - }); - - it('applies all arguments except the first', function () { - emitter - .on('a', function (a, b, c) { - expect(a).to.be('foo'); - expect(b).to.be('bar'); - expect(c).to.be('baz'); - }) - .emit('a', 'foo', 'bar', 'baz'); - }); - - it('uses the SimpleEmitter as the this context', function () { - emitter - .on('a', function () { - expect(this).to.be(emitter); - }) - .emit('a'); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/base_object.ts b/src/legacy/ui/public/utils/base_object.ts deleted file mode 100644 index 63c7ebf6de5bb..0000000000000 --- a/src/legacy/ui/public/utils/base_object.ts +++ /dev/null @@ -1,47 +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 angular from 'angular'; -import _ from 'lodash'; -// @ts-ignore -- awaiting https://github.com/w33ble/rison-node/issues/1 -import rison from 'rison-node'; - -export class BaseObject { - // Set the attributes or default to an empty object - constructor(attributes: Record = {}) { - // Set the attributes or default to an empty object - _.assign(this, attributes); - } - - public toObject() { - // return just the data. - return _.omit(this, (value: any, key: string) => { - return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value); - }); - } - - public toRISON() { - // Use Angular to remove the private vars, and JSON.stringify to serialize - return rison.encode(JSON.parse(angular.toJson(this))); - } - - public toJSON() { - return this.toObject(); - } -} diff --git a/src/legacy/ui/public/utils/find_by_param.ts b/src/legacy/ui/public/utils/find_by_param.ts deleted file mode 100644 index de32fc955a8cd..0000000000000 --- a/src/legacy/ui/public/utils/find_by_param.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -interface AnyObject { - [key: string]: any; -} - -// given an object or array of objects, return the value of the passed param -// if the param is missing, return undefined -export function findByParam(values: AnyObject | AnyObject[], param: string) { - if (Array.isArray(values)) { - // point series chart - const index = _.findIndex(values, param); - if (index === -1) { - return; - } - return values[index][param]; - } - return values[param]; // pie chart -} diff --git a/src/legacy/ui/public/utils/simple_emitter.js b/src/legacy/ui/public/utils/simple_emitter.js index 84397962c286b..503798ba160db 100644 --- a/src/legacy/ui/public/utils/simple_emitter.js +++ b/src/legacy/ui/public/utils/simple_emitter.js @@ -18,8 +18,6 @@ */ import _ from 'lodash'; -import { BaseObject } from './base_object'; -import { createLegacyClass } from './legacy_class'; /** * Simple event emitter class used in the vislib. Calls @@ -27,7 +25,6 @@ import { createLegacyClass } from './legacy_class'; * * @class */ -createLegacyClass(SimpleEmitter).inherits(BaseObject); export function SimpleEmitter() { this._listeners = {}; } @@ -134,4 +131,3 @@ SimpleEmitter.prototype.listenerCount = function (name) { return count + _.size(handlers); }, 0); }; - diff --git a/src/legacy/ui/public/utils/simple_emitter.test.js b/src/legacy/ui/public/utils/simple_emitter.test.js new file mode 100644 index 0000000000000..ff884a12be7ee --- /dev/null +++ b/src/legacy/ui/public/utils/simple_emitter.test.js @@ -0,0 +1,173 @@ +/* + * 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 { SimpleEmitter } from './simple_emitter'; +import sinon from 'sinon'; + +describe('SimpleEmitter class', () => { + let emitter; + + beforeEach(() => { + emitter = new SimpleEmitter(); + }); + + it('constructs an event emitter', () => { + expect(emitter).toHaveProperty('on'); + expect(emitter).toHaveProperty('off'); + expect(emitter).toHaveProperty('emit'); + expect(emitter).toHaveProperty('listenerCount'); + expect(emitter).toHaveProperty('removeAllListeners'); + }); + + describe('#listenerCount', () => { + it('counts all event listeners without any arg', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(1); + emitter.on('b', () => {}); + expect(emitter.listenerCount()).toBe(2); + }); + + it('limits to the event that is passed in', () => { + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(1); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + emitter.on('b', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + expect(emitter.listenerCount('b')).toBe(1); + expect(emitter.listenerCount()).toBe(3); + }); + }); + + describe('#on', () => { + it('registers a handler', () => { + const handler = sinon.stub(); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + + expect(handler.callCount).toBe(0); + emitter.emit('a'); + expect(handler.callCount).toBe(1); + }); + + it('allows multiple event handlers for the same event', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount('a')).toBe(2); + }); + + it('allows the same function to be registered multiple times', () => { + const handler = () => {}; + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(1); + emitter.on('a', handler); + expect(emitter.listenerCount()).toBe(2); + }); + }); + + describe('#off', () => { + it('removes a listener if it was registered', () => { + const handler = sinon.stub(); + expect(emitter.listenerCount()).toBe(0); + emitter.on('a', handler); + expect(emitter.listenerCount('a')).toBe(1); + emitter.off('a', handler); + expect(emitter.listenerCount('a')).toBe(0); + }); + + it('clears all listeners if no handler is passed', () => { + emitter.on('a', () => {}); + emitter.on('a', () => {}); + expect(emitter.listenerCount()).toBe(2); + emitter.off('a'); + expect(emitter.listenerCount()).toBe(0); + }); + + it('does not mind if the listener is not registered', () => { + emitter.off('a', () => {}); + }); + + it('does not mind if the event has no listeners', () => { + emitter.off('a'); + }); + }); + + describe('#emit', () => { + it('calls the handlers in the order they were defined', () => { + let i = 0; + const incr = () => ++i; + const one = sinon.spy(incr); + const two = sinon.spy(incr); + const three = sinon.spy(incr); + const four = sinon.spy(incr); + + emitter + .on('a', one) + .on('a', two) + .on('a', three) + .on('a', four) + .emit('a'); + + expect(one).toHaveProperty('callCount', 1); + expect(one.returned(1)).toBeDefined(); + + expect(two).toHaveProperty('callCount', 1); + expect(two.returned(2)).toBeDefined(); + + expect(three).toHaveProperty('callCount', 1); + expect(three.returned(3)).toBeDefined(); + + expect(four).toHaveProperty('callCount', 1); + expect(four.returned(4)).toBeDefined(); + }); + + it('always emits the handlers that were initially registered', () => { + const destructive = sinon.spy(() => { + emitter.removeAllListeners(); + expect(emitter.listenerCount()).toBe(0); + }); + const stub = sinon.stub(); + + emitter.on('run', destructive).on('run', stub).emit('run'); + + expect(destructive).toHaveProperty('callCount', 1); + expect(stub).toHaveProperty('callCount', 1); + }); + + it('applies all arguments except the first', () => { + emitter + .on('a', (a, b, c) => { + expect(a).toBe('foo'); + expect(b).toBe('bar'); + expect(c).toBe('baz'); + }) + .emit('a', 'foo', 'bar', 'baz'); + }); + + it('uses the SimpleEmitter as the this context', () => { + emitter + .on('a', function () { + expect(this).toBe(emitter); + }) + .emit('a'); + }); + }); +}); From f0eb4bb675e23bada8f170097e12260bb00803cc Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 11 Dec 2019 08:47:44 +0100 Subject: [PATCH 49/56] [APM] Fix some warnings logged in APM tests (#52487) * [APM] Fix some warnings logged in APM tests (Seemingly) since the React upgrade in 439708a6f9, our tests have started logging various warnings/errors to the console. The test suite is still passing but it creates a lot of noise. Changes: - use `act` or `wait` when appropriate - mock useFetcher calls - cleanup in useDelayedVisbility * Replace tick() with wait() --- .../__jest__/TransactionOverview.test.tsx | 3 ++ .../DatePicker/__test__/DatePicker.test.tsx | 6 +-- .../useDelayedVisibility/Delayed/index.ts | 6 +++ .../useDelayedVisibility/index.test.tsx | 41 +++++++++++++++---- .../shared/useDelayedVisibility/index.ts | 4 ++ .../__tests__/UrlParamsContext.test.tsx | 10 ++--- .../hooks/useFetcher.integration.test.tsx | 13 +++--- .../plugins/apm/public/utils/testHelpers.tsx | 4 -- 8 files changed, 61 insertions(+), 26 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx index a5356be72f5e4..91e0ae11a652e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx @@ -18,6 +18,7 @@ import { history } from '../../../../utils/history'; import { TransactionOverview } from '..'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import * as useServiceTransactionTypesHook from '../../../../hooks/useServiceTransactionTypes'; +import * as useFetcherHook from '../../../../hooks/useFetcher'; import { fromQuery } from '../../../shared/Links/url_helpers'; import { Router } from 'react-router-dom'; import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; @@ -51,6 +52,8 @@ function setup({ .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes') .mockReturnValue(serviceTransactionTypes); + jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); + return render( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx index 05094c59712a9..32379325c4020 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx @@ -10,13 +10,13 @@ import { UrlParamsContext, useUiFilters } from '../../../../context/UrlParamsContext'; -import { tick } from '../../../../utils/testHelpers'; import { DatePicker } from '../index'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { history } from '../../../../utils/history'; import { mount } from 'enzyme'; import { EuiSuperDatePicker } from '@elastic/eui'; import { MemoryRouter } from 'react-router-dom'; +import { wait } from '@testing-library/react'; const mockHistoryPush = jest.spyOn(history, 'push'); const mockRefreshTimeRange = jest.fn(); @@ -84,7 +84,7 @@ describe('DatePicker', () => { }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); - await tick(); + await wait(); expect(mockRefreshTimeRange).toHaveBeenCalled(); wrapper.unmount(); }); @@ -94,7 +94,7 @@ describe('DatePicker', () => { mountDatePicker({ refreshPaused: true, refreshInterval: 1000 }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); - await tick(); + await wait(); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts index 798e872dbc472..9048afe57153d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/Delayed/index.ts @@ -57,4 +57,10 @@ export class Delayed { public onChange(onChangeCallback: Callback) { this.onChangeCallback = onChangeCallback; } + + public destroy() { + if (this.timeoutId) { + window.clearTimeout(this.timeoutId); + } + } } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx index 57e634df22837..c55c6ab351848 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx @@ -4,11 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook } from '@testing-library/react-hooks'; +import { + renderHook, + act, + RenderHookResult +} from '@testing-library/react-hooks'; import { useDelayedVisibility } from '.'; describe('useFetcher', () => { - let hook; + let hook: RenderHookResult; + beforeEach(() => { jest.useFakeTimers(); }); @@ -26,9 +31,15 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(10); + act(() => { + jest.advanceTimersByTime(10); + }); + expect(hook.result.current).toEqual(false); - jest.advanceTimersByTime(50); + act(() => { + jest.advanceTimersByTime(50); + }); + expect(hook.result.current).toEqual(true); }); @@ -38,8 +49,11 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(100); + act(() => { + jest.advanceTimersByTime(100); + }); hook.rerender(false); + expect(hook.result.current).toEqual(true); }); @@ -49,11 +63,22 @@ describe('useFetcher', () => { }); hook.rerender(true); - jest.advanceTimersByTime(100); + + act(() => { + jest.advanceTimersByTime(100); + }); + hook.rerender(false); - jest.advanceTimersByTime(900); + act(() => { + jest.advanceTimersByTime(900); + }); + expect(hook.result.current).toEqual(true); - jest.advanceTimersByTime(100); + + act(() => { + jest.advanceTimersByTime(100); + }); + expect(hook.result.current).toEqual(false); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts index 5acbbd1d45737..c4465c7b42339 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.ts @@ -26,6 +26,10 @@ export function useDelayedVisibility( setIsVisible(visibility); }); delayedRef.current = delayed; + + return () => { + delayed.destroy(); + }; }, [hideDelayMs, showDelayMs, minimumVisibleDuration]); useEffect(() => { diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx index 2604a3a122574..d2d8036e864ae 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/__tests__/UrlParamsContext.test.tsx @@ -11,8 +11,8 @@ import { Location, History } from 'history'; import { MemoryRouter, Router } from 'react-router-dom'; import moment from 'moment-timezone'; import { IUrlParams } from '../types'; -import { tick } from '../../../utils/testHelpers'; import { getParsedDate } from '../helpers'; +import { wait } from '@testing-library/react'; function mountParams(location: Location) { return mount( @@ -143,13 +143,13 @@ describe('UrlParamsContext', () => { ); - await tick(); + await wait(); expect(calls.length).toBe(1); wrapper.find('button').simulate('click'); - await tick(); + await wait(); expect(calls.length).toBe(2); @@ -194,11 +194,11 @@ describe('UrlParamsContext', () => { ); - await tick(); + await wait(); wrapper.find('button').simulate('click'); - await tick(); + await wait(); const params = getDataFromOutput(wrapper); expect(params.start).toEqual('2000-06-14T00:00:00.000Z'); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx index 36a8377c02527..743cf4e01e555 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import { delay, tick } from '../utils/testHelpers'; +import { render, wait } from '@testing-library/react'; +import { delay } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; import { LegacyCoreStart } from 'kibana/public'; @@ -76,7 +76,8 @@ describe('when simulating race condition', () => { it('should render "Hello from Peter" after 200ms', async () => { jest.advanceTimersByTime(200); - await tick(); + + await wait(); expect(renderSpy).lastCalledWith({ data: 'Hello from Peter', @@ -87,7 +88,7 @@ describe('when simulating race condition', () => { it('should render "Hello from Peter" after 600ms', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(renderSpy).lastCalledWith({ data: 'Hello from Peter', @@ -98,7 +99,7 @@ describe('when simulating race condition', () => { it('should should NOT have rendered "Hello from John" at any point', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(renderSpy).not.toHaveBeenCalledWith({ data: 'Hello from John', @@ -109,7 +110,7 @@ describe('when simulating race condition', () => { it('should send and receive calls in the right order', async () => { jest.advanceTimersByTime(600); - await tick(); + await wait(); expect(requestCallOrder).toEqual([ ['request', 'John', 500], diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index b5cee4a78b01c..9e3c486715a1f 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -58,7 +58,6 @@ export async function getRenderedHref(Component: React.FC, location: Location) { ); - await tick(); await waitForElement(() => el.container.querySelector('a')); const a = el.container.querySelector('a'); @@ -74,9 +73,6 @@ export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -// Await this when you need to "flush" promises to immediately resolve or throw in tests -export const tick = () => new Promise(resolve => setImmediate(resolve, 0)); - export function expectTextsNotInDocument(output: any, texts: string[]) { texts.forEach(text => { try { From 7e27f0d35f87966499ba9f717d9c5068efe51174 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 11 Dec 2019 08:55:46 +0100 Subject: [PATCH 50/56] Decouple Authorization subsystem from Legacy API. (#52638) --- x-pack/legacy/plugins/security/index.js | 1 - .../authorization/check_privileges.test.ts | 6 +++--- .../server/authorization/check_privileges.ts | 11 ++++++----- .../security/server/authorization/index.mock.ts | 7 +++++-- .../security/server/authorization/index.test.ts | 7 +++---- .../security/server/authorization/index.ts | 16 ++++++++-------- x-pack/plugins/security/server/plugin.ts | 10 ++++++---- .../routes/authorization/roles/get.test.ts | 2 +- .../server/routes/authorization/roles/get.ts | 2 +- .../routes/authorization/roles/get_all.test.ts | 2 +- .../server/routes/authorization/roles/get_all.ts | 6 +----- .../routes/authorization/roles/put.test.ts | 2 +- .../server/routes/authorization/roles/put.ts | 2 +- 13 files changed, 37 insertions(+), 37 deletions(-) diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 1d798a4a2bc40..115dd8b9b8206 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -132,7 +132,6 @@ export const security = (kibana) => new kibana.Plugin({ server.plugins.kibana.systemApi ), cspRules: createCSPRuleString(config.get('csp.rules')), - kibanaIndexName: config.get('kibana.index'), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts index b1cb78008da00..8c1241937892e 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts @@ -48,7 +48,7 @@ describe('#atSpace', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); @@ -291,7 +291,7 @@ describe('#atSpaces', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); @@ -772,7 +772,7 @@ describe('#globally', () => { const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( mockActions, mockClusterClient, - () => application + application ); const request = httpServerMock.createKibanaRequest(); const checkPrivileges = checkPrivilegesWithRequest(request); diff --git a/x-pack/plugins/security/server/authorization/check_privileges.ts b/x-pack/plugins/security/server/authorization/check_privileges.ts index 5bc3ce075452d..3ef7a8f29a0bf 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.ts @@ -61,7 +61,7 @@ export interface CheckPrivileges { export function checkPrivilegesWithRequestFactory( actions: CheckPrivilegesActions, clusterClient: IClusterClient, - getApplicationName: () => string + applicationName: string ) { const hasIncompatibleVersion = ( applicationPrivilegesResponse: HasPrivilegesResponseApplication @@ -81,23 +81,24 @@ export function checkPrivilegesWithRequestFactory( : [privilegeOrPrivileges]; const allApplicationPrivileges = uniq([actions.version, actions.login, ...privileges]); - const application = getApplicationName(); const hasPrivilegesResponse = (await clusterClient .asScoped(request) .callAsCurrentUser('shield.hasPrivileges', { body: { - applications: [{ application, resources, privileges: allApplicationPrivileges }], + applications: [ + { application: applicationName, resources, privileges: allApplicationPrivileges }, + ], }, })) as HasPrivilegesResponse; validateEsPrivilegeResponse( hasPrivilegesResponse, - application, + applicationName, allApplicationPrivileges, resources ); - const applicationPrivilegesResponse = hasPrivilegesResponse.application[application]; + const applicationPrivilegesResponse = hasPrivilegesResponse.application[applicationName]; if (hasIncompatibleVersion(applicationPrivilegesResponse)) { throw new Error( diff --git a/x-pack/plugins/security/server/authorization/index.mock.ts b/x-pack/plugins/security/server/authorization/index.mock.ts index 2e700745c69dc..930ede4157723 100644 --- a/x-pack/plugins/security/server/authorization/index.mock.ts +++ b/x-pack/plugins/security/server/authorization/index.mock.ts @@ -8,12 +8,15 @@ import { Actions } from '.'; import { AuthorizationMode } from './mode'; export const authorizationMock = { - create: ({ version = 'mock-version' }: { version?: string } = {}) => ({ + create: ({ + version = 'mock-version', + applicationName = 'mock-application', + }: { version?: string; applicationName?: string } = {}) => ({ actions: new Actions(version), checkPrivilegesWithRequest: jest.fn(), checkPrivilegesDynamicallyWithRequest: jest.fn(), checkSavedObjectsPrivilegesWithRequest: jest.fn(), - getApplicationName: jest.fn().mockReturnValue('mock-application'), + applicationName, mode: { useRbacForRequest: jest.fn() } as jest.Mocked, privileges: { get: jest.fn() }, registerPrivilegesWithCluster: jest.fn(), diff --git a/x-pack/plugins/security/server/authorization/index.test.ts b/x-pack/plugins/security/server/authorization/index.test.ts index 24179e062230a..34b9efea77165 100644 --- a/x-pack/plugins/security/server/authorization/index.test.ts +++ b/x-pack/plugins/security/server/authorization/index.test.ts @@ -53,7 +53,6 @@ test(`returns exposed services`, () => { .fn() .mockReturnValue({ getSpaceId: jest.fn(), namespaceToSpaceId: jest.fn() }); const mockFeaturesService = { getFeatures: () => [] }; - const mockGetLegacyAPI = () => ({ kibanaIndexName }); const mockLicense = licenseMock.create(); const authz = setupAuthorization({ @@ -61,20 +60,20 @@ test(`returns exposed services`, () => { clusterClient: mockClusterClient, license: mockLicense, loggers: loggingServiceMock.create(), - getLegacyAPI: mockGetLegacyAPI, + kibanaIndexName, packageVersion: 'some-version', featuresService: mockFeaturesService, getSpacesService: mockGetSpacesService, }); expect(authz.actions.version).toBe('version:some-version'); - expect(authz.getApplicationName()).toBe(application); + expect(authz.applicationName).toBe(application); expect(authz.checkPrivilegesWithRequest).toBe(mockCheckPrivilegesWithRequest); expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith( authz.actions, mockClusterClient, - authz.getApplicationName + authz.applicationName ); expect(authz.checkPrivilegesDynamicallyWithRequest).toBe( diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index b5f9efadbd8d0..41e6d12eb8f36 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -12,7 +12,7 @@ import { IClusterClient, } from '../../../../../src/core/server'; -import { FeaturesService, LegacyAPI, SpacesService } from '../plugin'; +import { FeaturesService, SpacesService } from '../plugin'; import { Actions } from './actions'; import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges'; import { @@ -43,7 +43,7 @@ interface SetupAuthorizationParams { license: SecurityLicense; loggers: LoggerFactory; featuresService: FeaturesService; - getLegacyAPI(): Pick; + kibanaIndexName: string; getSpacesService(): SpacesService | undefined; } @@ -52,7 +52,7 @@ export interface Authorization { checkPrivilegesWithRequest: CheckPrivilegesWithRequest; checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest; checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest; - getApplicationName: () => string; + applicationName: string; mode: AuthorizationMode; privileges: PrivilegesService; disableUnauthorizedCapabilities: ( @@ -69,23 +69,23 @@ export function setupAuthorization({ license, loggers, featuresService, - getLegacyAPI, + kibanaIndexName, getSpacesService, }: SetupAuthorizationParams): Authorization { const actions = new Actions(packageVersion); const mode = authorizationModeFactory(license); - const getApplicationName = () => `${APPLICATION_PREFIX}${getLegacyAPI().kibanaIndexName}`; + const applicationName = `${APPLICATION_PREFIX}${kibanaIndexName}`; const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( actions, clusterClient, - getApplicationName + applicationName ); const privileges = privilegesFactory(actions, featuresService); const logger = loggers.get('authorization'); const authz = { actions, - getApplicationName, + applicationName, checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: checkPrivilegesDynamicallyWithRequestFactory( checkPrivilegesWithRequest, @@ -123,7 +123,7 @@ export function setupAuthorization({ registerPrivilegesWithCluster: async () => { validateFeaturePrivileges(actions, featuresService.getFeatures()); - await registerPrivilegesWithCluster(logger, privileges, getApplicationName(), clusterClient); + await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient); }, }; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cb197ecaf7e10..84d448331cef2 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subscription } from 'rxjs'; +import { Subscription, combineLatest } from 'rxjs'; import { first } from 'rxjs/operators'; import { IClusterClient, @@ -42,7 +42,6 @@ export type FeaturesService = Pick; */ export interface LegacyAPI { isSystemAPIRequest: (request: KibanaRequest) => boolean; - kibanaIndexName: string; cspRules: string; savedObjects: SavedObjectsLegacyService; auditLogger: { @@ -121,7 +120,10 @@ export class Plugin { core: CoreSetup, { features, licensing }: PluginSetupDependencies ): Promise> { - const config = await createConfig$(this.initializerContext, core.http.isTlsEnabled) + const [config, legacyConfig] = await combineLatest([ + createConfig$(this.initializerContext, core.http.isTlsEnabled), + this.initializerContext.config.legacy.globalConfig$, + ]) .pipe(first()) .toPromise(); @@ -148,7 +150,7 @@ export class Plugin { clusterClient: this.clusterClient, license, loggers: this.initializerContext.logger, - getLegacyAPI: this.getLegacyAPI, + kibanaIndexName: legacyConfig.kibana.index, packageVersion: this.initializerContext.env.packageInfo.version, getSpacesService: this.getSpacesService, featuresService: features, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index 1cfc1ae416ae4..447d890605cc9 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -36,7 +36,7 @@ describe('GET role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index be69e222dd093..1173d37cba64f 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -28,7 +28,7 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi body: transformElasticsearchRoleToRole( elasticsearchRole, request.params.name, - authz.getApplicationName() + authz.applicationName ), }); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 76ce6a272e285..3bd85122c95d1 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -31,7 +31,7 @@ describe('GET all roles', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index f5d2d51280fc4..6ff431f0f8b6a 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -22,11 +22,7 @@ export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteD return response.ok({ body: Object.entries(elasticsearchRoles) .map(([roleName, elasticsearchRole]) => - transformElasticsearchRoleToRole( - elasticsearchRole, - roleName, - authz.getApplicationName() - ) + transformElasticsearchRoleToRole(elasticsearchRole, roleName, authz.applicationName) ) .sort((roleA, roleB) => { if (roleA.name < roleB.name) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 31963987c2efb..cb80549df8417 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -62,7 +62,7 @@ const putRoleTest = ( ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authz.getApplicationName.mockReturnValue(application); + mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 92c940132e660..e0245e7260446 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -42,7 +42,7 @@ export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefi const body = transformPutPayloadToElasticsearchRole( request.body, - authz.getApplicationName(), + authz.applicationName, rawRoles[name] ? rawRoles[name].applications : [] ); From aa31b535d1ff2f732deb9a3fd7d564a8e5f26431 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 11 Dec 2019 09:54:42 +0100 Subject: [PATCH 51/56] [Watcher] New Platform (NP) Migration (#50908) * First iteration of watch public -> new platform Still need to switch to np ready version of use_request * - Switched to using np ready request - Some updates after API changes * First attempt at server shim * Rename file and re-enable react hooks linting * Fix some public types and react hooks lint rules * Fix types * More ES lint react hooks fixes * Migrated server lib -> ts. Part way done with migrating routes to NP router and TS * Big subset of routes to TS and NP router - almost there * Delete legacy error wrappers and moved last set of routes to TS and NP router * Remove @ts-ignore's and update route registration to use shim with http router * Added routes validations, fixes for hooks and fixes for types * Fix more types and finish testing API routes * Fix usage of feature catalogue and fix time buckets types * Fix error message shape [skip ci] * Split legacy from new platform dependencies server-side * Refactor: Seperate client legacy and NP dependencies * Add file: added types file * Fix UISettings client type import * Update license pre-routing factory spec * Update variable names, use of I18nContext (use NP) and docs * Use NP elasticsearchclient * Simplify is_es_error_factory * Fix types * Improve code legibility and remove second use of `useAppContext` * Use @kbn/config-schema (not validate: false) on routes! * Fix watch create JSON spec * Create threshold test working * Unskip watch_edit.test.ts * Unskip watch_list.test.ts * Done re-enabling component integration tests * TimeBuckets typo + remove unnecessary // @ts-ignore --- .eslintrc.js | 7 - src/legacy/ui/public/time_buckets/index.d.ts | 22 +++ .../public/request/np_ready_request.ts | 9 +- x-pack/dev-tools/jest/create_jest_config.js | 3 +- .../helpers/app_context.mock.tsx | 50 ++++++ .../helpers/body_response.ts} | 4 +- .../helpers/http_requests.ts | 2 +- .../client_integration/helpers/index.ts | 2 +- .../helpers/setup_environment.ts | 12 +- .../helpers/watch_create_json.helpers.ts | 7 +- .../helpers/watch_create_threshold.helpers.ts | 7 +- .../helpers/watch_edit.helpers.ts | 7 +- .../helpers/watch_list.helpers.ts | 5 +- .../helpers/watch_status.helpers.ts | 5 +- .../watch_create_json.test.ts | 19 +-- .../watch_create_threshold.test.tsx | 130 ++++++---------- .../client_integration/watch_edit.test.ts | 33 +--- .../client_integration/watch_list.test.ts | 9 +- .../client_integration/watch_status.test.ts | 9 +- x-pack/legacy/plugins/watcher/kibana.json | 9 ++ .../plugins/watcher/plugin_definition.js | 46 ------ .../plugins/watcher/plugin_definition.ts | 32 ++++ x-pack/legacy/plugins/watcher/public/app.html | 3 - .../legacy/plugins/watcher/public/legacy.ts | 146 ++++++++++++++++++ .../documentation_links.ts | 24 --- ...fecycle.js => manage_angular_lifecycle.ts} | 11 +- .../plugins/watcher/public/models/index.d.ts | 42 ----- .../{app.js => np_ready/application/app.tsx} | 79 ++++++---- .../np_ready/application/app_context.tsx | 65 ++++++++ .../public/np_ready/application/boot.tsx | 35 +++++ .../components/confirm_watches_modal.tsx | 0 .../components/delete_watches_modal.tsx | 7 +- .../application}/components/form_errors.tsx | 0 .../application}/components/index.ts | 0 .../components/page_error/index.ts | 0 .../components/page_error/page_error.tsx | 0 .../page_error/page_error_forbidden.tsx | 0 .../page_error/page_error_not_exist.tsx | 0 .../application}/components/section_error.tsx | 20 ++- .../components/section_loading.tsx | 0 .../application}/components/watch_status.tsx | 2 +- .../application}/constants/base_path.ts | 0 .../application}/constants/index.ts | 0 .../{ => np_ready/application}/index.scss | 0 .../{ => np_ready/application}/lib/api.ts | 96 +++++------- .../application}/lib/breadcrumbs.ts | 0 .../application}/lib/format_date.ts | 0 .../application}/lib/get_search_value.ts | 0 .../application}/lib/get_time_unit_label.ts | 2 +- .../application}/lib/navigation.ts | 0 .../application}/lib/use_request.ts | 1 + .../application}/models/action/action.js | 2 +- .../application}/models/action/base_action.js | 0 .../models/action/email_action.js | 0 .../application}/models/action/index.js | 0 .../models/action/index_action.js | 0 .../application}/models/action/jira_action.js | 0 .../models/action/logging_action.js | 0 .../models/action/pagerduty_action.js | 0 .../models/action/slack_action.js | 0 .../models/action/unknown_action.js | 0 .../models/action/webhook_action.js | 0 .../models/action_status/action_status.js | 2 +- .../models/action_status/index.js | 0 .../models/execute_details/execute_details.js | 0 .../models/execute_details/index.js | 0 .../np_ready/application/models/index.d.ts | 39 +++++ .../application}/models/settings/index.js | 0 .../application}/models/settings/settings.js | 0 .../models/visualize_options/index.js | 0 .../visualize_options/visualize_options.js | 0 .../application}/models/watch/agg_types.ts | 2 +- .../application}/models/watch/base_watch.js | 0 .../application}/models/watch/comparators.ts | 2 +- .../models/watch/default_watch.json | 0 .../models/watch/group_by_types.ts | 0 .../application}/models/watch/index.js | 0 .../application}/models/watch/json_watch.js | 2 +- .../check_action_id_collision.js | 0 .../lib/check_action_id_collision/index.js | 0 .../lib/create_action_id/create_action_id.js | 0 .../watch/lib/create_action_id/index.js | 0 .../models/watch/monitoring_watch.js | 2 +- .../models/watch/threshold_watch.js | 2 +- .../application}/models/watch/watch.js | 2 +- .../application}/models/watch_errors/index.js | 0 .../models/watch_errors/watch_errors.js | 0 .../models/watch_history_item/index.js | 0 .../watch_history_item/watch_history_item.js | 2 +- .../application}/models/watch_status/index.js | 0 .../models/watch_status/watch_status.js | 2 +- .../components/json_watch_edit/index.ts | 0 .../json_watch_edit/json_watch_edit.tsx | 8 +- .../json_watch_edit/json_watch_edit_form.tsx | 17 +- .../json_watch_edit_simulate.tsx | 13 +- .../json_watch_edit_simulate_results.tsx | 2 +- .../components/monitoring_watch_edit/index.ts | 0 .../monitoring_watch_edit.tsx | 0 .../watch_edit/components/request_flyout.tsx | 0 .../action_fields/email_action_fields.tsx | 2 +- .../action_fields/index.ts | 0 .../action_fields/index_action_fields.tsx | 2 +- .../action_fields/jira_action_fields.tsx | 2 +- .../action_fields/logging_action_fields.tsx | 2 +- .../action_fields/pagerduty_action_fields.tsx | 2 +- .../action_fields/slack_action_fields.tsx | 2 +- .../action_fields/webhook_action_fields.tsx | 4 +- .../components/threshold_watch_edit/index.ts | 0 .../threshold_watch_action_accordion.tsx | 23 +-- .../threshold_watch_action_dropdown.tsx | 4 +- .../threshold_watch_action_panel.tsx | 4 +- .../threshold_watch_edit.tsx | 66 ++++---- .../watch_visualization.tsx | 83 +++++----- .../watch_edit/components/watch_edit.tsx | 49 +++--- .../sections/watch_edit/watch_context.ts | 0 .../sections/watch_edit/watch_edit_actions.ts | 18 +-- .../watch_list/components/watch_list.tsx | 19 ++- .../watch_status/components/watch_detail.tsx | 9 +- .../watch_status/components/watch_history.tsx | 8 +- .../watch_status/components/watch_status.tsx | 19 ++- .../watch_status/watch_details_context.ts | 0 .../application}/shared_imports.ts | 2 +- .../public/{index.js => np_ready/index.ts} | 4 +- .../plugins/watcher/public/np_ready/plugin.ts | 62 ++++++++ .../np_ready/types.ts} | 8 +- .../watcher/public/register_feature.js | 24 --- .../watcher/public/register_feature.ts | 21 +++ .../public/register_management_sections.js | 60 ------- .../plugins/watcher/public/register_route.js | 68 -------- .../call_with_internal_user_factory.js | 18 --- .../call_with_request_factory.js | 21 --- .../lib/call_with_request_factory/index.js | 7 - .../lib/elasticsearch_js_plugin/index.js | 7 - .../__tests__/wrap_custom_error.js | 21 --- .../error_wrappers/__tests__/wrap_es_error.js | 39 ----- .../__tests__/wrap_unknown_error.js | 19 --- .../server/lib/error_wrappers/index.js | 9 -- .../lib/error_wrappers/wrap_custom_error.js | 18 --- .../lib/error_wrappers/wrap_es_error.js | 30 ---- .../lib/error_wrappers/wrap_unknown_error.js | 17 -- .../__tests__/is_es_error_factory.js | 48 ------ .../server/lib/is_es_error_factory/index.js | 7 - .../is_es_error_factory.js | 18 --- .../license_pre_routing_factory.js | 31 ---- .../index.js => np_ready/index.ts} | 4 +- .../np_ready/lib/call_with_request_factory.ts | 28 ++++ .../lib/elasticsearch_js_plugin.ts} | 98 ++++++------ .../__tests__/fetch_all_from_scroll.js | 0 .../fetch_all_from_scroll.ts} | 15 +- .../lib/fetch_all_from_scroll/index.ts} | 0 .../np_ready/lib/is_es_error}/index.ts | 2 +- .../lib/is_es_error/is_es_error.ts} | 8 +- .../__tests__/license_pre_routing_factory.js | 23 +-- .../lib/license_pre_routing_factory/index.ts} | 0 .../license_pre_routing_factory.ts | 43 ++++++ .../lib/normalized_field_types/index.ts} | 0 .../normalized_field_types.ts} | 16 +- .../action_status/__tests__/action_status.js | 2 +- .../models/action_status/action_status.js | 4 +- .../models/action_status/index.js | 0 .../__tests__/execute_details.js | 0 .../models/execute_details/execute_details.js | 0 .../models/execute_details/index.js | 0 .../models/fields/__tests__/fields.js | 0 .../{ => np_ready}/models/fields/fields.js | 0 .../{ => np_ready}/models/fields/index.js | 0 .../models/settings/__tests__/settings.js | 0 .../{ => np_ready}/models/settings/index.js | 0 .../models/settings/settings.js | 2 +- .../models/visualize_options/index.js | 0 .../visualize_options/visualize_options.js | 0 .../{ => np_ready}/models/watch/base_watch.js | 2 +- .../models/watch/base_watch.test.js | 0 .../{ => np_ready}/models/watch/index.js | 0 .../{ => np_ready}/models/watch/json_watch.js | 4 +- .../models/watch/json_watch.test.js | 0 .../lib/get_watch_type/get_watch_type.js | 2 +- .../models/watch/lib/get_watch_type/index.js | 0 .../models/watch/monitoring_watch.js | 2 +- .../models/watch/monitoring_watch.test.js | 0 .../__tests__/format_visualize_data.js | 2 +- .../threshold_watch/build_visualize_query.js | 4 +- .../threshold_watch/data_samples/count.json | 0 .../data_samples/count.query.date.json | 0 .../data_samples/count.query.json | 0 .../data_samples/count_terms.json | 0 .../data_samples/count_terms.query.date.json | 0 .../data_samples/count_terms.query.json | 0 .../data_samples/non_count.json | 0 .../data_samples/non_count.query.date.json | 0 .../data_samples/non_count.query.json | 0 .../data_samples/non_count_terms.json | 0 .../non_count_terms.query.date.json | 0 .../data_samples/non_count_terms.query.json | 0 .../threshold_watch/format_visualize_data.js | 2 +- .../models/watch/threshold_watch/index.js | 0 .../watch/threshold_watch/threshold_watch.js | 4 +- .../threshold_watch/threshold_watch.test.js | 2 +- .../{ => np_ready}/models/watch/watch.js | 2 +- .../{ => np_ready}/models/watch/watch.test.js | 2 +- .../models/watch_errors/index.js | 0 .../models/watch_errors/watch_errors.js | 0 .../models/watch_errors/watch_errors.test.js | 0 .../__tests__/watch_history_item.js | 0 .../models/watch_history_item/index.js | 0 .../watch_history_item/watch_history_item.js | 2 +- .../watch_status/__tests__/watch_status.js | 2 +- .../models/watch_status/index.js | 0 .../models/watch_status/watch_status.js | 4 +- .../plugins/watcher/server/np_ready/plugin.ts | 51 ++++++ .../routes/api/indices/index.ts} | 0 .../routes/api/indices/register_get_route.ts | 92 +++++++++++ .../api/indices/register_indices_routes.ts} | 5 +- .../routes/api/license/index.ts} | 0 .../api/license/register_license_routes.ts} | 5 +- .../api/license/register_refresh_route.ts} | 25 +-- .../routes/api/register_list_fields_route.ts | 65 ++++++++ .../routes/api/register_load_history_route.ts | 77 +++++++++ .../routes/api/settings/index.ts} | 0 .../api/settings/register_load_route.ts | 43 ++++++ .../api/settings/register_settings_routes.ts} | 5 +- .../routes/api/watch/action/index.ts} | 0 .../action/register_acknowledge_route.ts | 65 ++++++++ .../watch/action/register_action_routes.ts} | 5 +- .../routes/api/watch/index.ts} | 0 .../api/watch/register_activate_route.ts | 66 ++++++++ .../api/watch/register_deactivate_route.ts | 65 ++++++++ .../routes/api/watch/register_delete_route.ts | 52 +++++++ .../api/watch/register_execute_route.ts | 78 ++++++++++ .../api/watch/register_history_route.ts | 97 ++++++++++++ .../routes/api/watch/register_load_route.ts | 69 +++++++++ .../routes/api/watch/register_save_route.ts | 104 +++++++++++++ .../api/watch/register_visualize_route.ts | 70 +++++++++ .../api/watch/register_watch_routes.ts} | 21 +-- .../routes/api/watches/index.ts} | 0 .../api/watches/register_delete_route.ts | 63 ++++++++ .../routes/api/watches/register_list_route.ts | 86 +++++++++++ .../api/watches/register_watches_routes.ts} | 7 +- .../plugins/watcher/server/np_ready/types.ts | 22 +++ .../routes/api/fields/register_list_route.js | 60 ------- .../server/routes/api/history/index.js | 7 - .../routes/api/history/register_load_route.js | 78 ---------- .../routes/api/indices/register_get_route.js | 89 ----------- .../api/settings/register_load_route.js | 47 ------ .../action/register_acknowledge_route.js | 63 -------- .../api/watch/register_activate_route.js | 63 -------- .../api/watch/register_deactivate_route.js | 63 -------- .../routes/api/watch/register_delete_route.js | 50 ------ .../api/watch/register_execute_route.js | 69 --------- .../api/watch/register_history_route.js | 89 ----------- .../routes/api/watch/register_load_route.js | 68 -------- .../routes/api/watch/register_save_route.js | 94 ----------- .../api/watch/register_visualize_route.js | 62 -------- .../api/watches/register_delete_route.js | 58 ------- .../routes/api/watches/register_list_route.js | 79 ---------- 255 files changed, 2290 insertions(+), 2206 deletions(-) create mode 100644 src/legacy/ui/public/time_buckets/index.d.ts create mode 100644 x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx rename x-pack/legacy/plugins/watcher/{server/routes/api/fields/index.js => __jest__/client_integration/helpers/body_response.ts} (56%) create mode 100644 x-pack/legacy/plugins/watcher/kibana.json delete mode 100644 x-pack/legacy/plugins/watcher/plugin_definition.js create mode 100644 x-pack/legacy/plugins/watcher/plugin_definition.ts delete mode 100644 x-pack/legacy/plugins/watcher/public/app.html create mode 100644 x-pack/legacy/plugins/watcher/public/legacy.ts delete mode 100644 x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts rename x-pack/legacy/plugins/watcher/public/{lib/manage_angular_lifecycle.js => manage_angular_lifecycle.ts} (75%) delete mode 100644 x-pack/legacy/plugins/watcher/public/models/index.d.ts rename x-pack/legacy/plugins/watcher/public/{app.js => np_ready/application/app.tsx} (60%) create mode 100644 x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx create mode 100644 x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/confirm_watches_modal.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/delete_watches_modal.tsx (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/form_errors.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/page_error/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/page_error/page_error.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/page_error/page_error_forbidden.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/page_error/page_error_not_exist.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/section_error.tsx (80%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/section_loading.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/components/watch_status.tsx (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/constants/base_path.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/constants/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/index.scss (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/api.ts (61%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/breadcrumbs.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/format_date.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/get_search_value.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/get_time_unit_label.ts (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/navigation.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/lib/use_request.ts (99%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/action.js (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/base_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/email_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/index_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/jira_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/logging_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/pagerduty_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/slack_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/unknown_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action/webhook_action.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action_status/action_status.js (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/action_status/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/execute_details/execute_details.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/execute_details/index.js (100%) create mode 100644 x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/settings/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/settings/settings.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/visualize_options/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/visualize_options/visualize_options.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/agg_types.ts (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/base_watch.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/comparators.ts (96%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/default_watch.json (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/group_by_types.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/json_watch.js (98%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/lib/check_action_id_collision/check_action_id_collision.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/lib/check_action_id_collision/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/lib/create_action_id/create_action_id.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/lib/create_action_id/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/monitoring_watch.js (92%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/threshold_watch.js (99%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch/watch.js (93%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_errors/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_errors/watch_errors.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_history_item/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_history_item/watch_history_item.js (91%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_status/index.js (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/models/watch_status/watch_status.js (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/json_watch_edit/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx (92%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx (96%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx (99%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/monitoring_watch_edit/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/request_flyout.tsx (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx (97%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx (97%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx (96%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx (98%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/index.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx (91%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx (96%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx (93%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx (95%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx (83%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/components/watch_edit.tsx (82%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/watch_context.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_edit/watch_edit_actions.ts (86%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_list/components/watch_list.tsx (97%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_status/components/watch_detail.tsx (96%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_status/components/watch_history.tsx (97%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_status/components/watch_status.tsx (94%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/sections/watch_status/watch_details_context.ts (100%) rename x-pack/legacy/plugins/watcher/public/{ => np_ready/application}/shared_imports.ts (79%) rename x-pack/legacy/plugins/watcher/public/{index.js => np_ready/index.ts} (71%) create mode 100644 x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts rename x-pack/legacy/plugins/watcher/{server/routes/api/fields/register_fields_routes.js => public/np_ready/types.ts} (63%) delete mode 100644 x-pack/legacy/plugins/watcher/public/register_feature.js create mode 100644 x-pack/legacy/plugins/watcher/public/register_feature.ts delete mode 100644 x-pack/legacy/plugins/watcher/public/register_management_sections.js delete mode 100644 x-pack/legacy/plugins/watcher/public/register_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js delete mode 100644 x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js rename x-pack/legacy/plugins/watcher/server/{lib/call_with_internal_user_factory/index.js => np_ready/index.ts} (55%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts rename x-pack/legacy/plugins/watcher/server/{lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js => np_ready/lib/elasticsearch_js_plugin.ts} (84%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js (100%) rename x-pack/legacy/plugins/watcher/server/{lib/fetch_all_from_scroll/fetch_all_from_scroll.js => np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts} (64%) rename x-pack/legacy/plugins/watcher/server/{lib/fetch_all_from_scroll/index.js => np_ready/lib/fetch_all_from_scroll/index.ts} (100%) rename x-pack/legacy/plugins/watcher/{public/lib/documentation_links => server/np_ready/lib/is_es_error}/index.ts (84%) rename x-pack/legacy/plugins/watcher/server/{routes/api/history/register_history_routes.js => np_ready/lib/is_es_error/is_es_error.ts} (55%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js (71%) rename x-pack/legacy/plugins/watcher/server/{lib/license_pre_routing_factory/index.js => np_ready/lib/license_pre_routing_factory/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts rename x-pack/legacy/plugins/watcher/server/{lib/normalized_field_types/index.js => np_ready/lib/normalized_field_types/index.ts} (100%) rename x-pack/legacy/plugins/watcher/server/{lib/normalized_field_types/normalized_field_types.js => np_ready/lib/normalized_field_types/normalized_field_types.ts} (61%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/action_status/__tests__/action_status.js (99%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/action_status/action_status.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/action_status/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/execute_details/__tests__/execute_details.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/execute_details/execute_details.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/execute_details/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/fields/__tests__/fields.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/fields/fields.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/fields/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/settings/__tests__/settings.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/settings/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/settings/settings.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/visualize_options/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/visualize_options/visualize_options.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/base_watch.js (98%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/base_watch.test.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/json_watch.js (93%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/json_watch.test.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/lib/get_watch_type/get_watch_type.js (88%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/lib/get_watch_type/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/monitoring_watch.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/monitoring_watch.test.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/__tests__/format_visualize_data.js (99%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/build_visualize_query.js (95%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count.query.date.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count.query.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count_terms.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count_terms.query.date.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/count_terms.query.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count.query.date.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count.query.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count_terms.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/data_samples/non_count_terms.query.json (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/format_visualize_data.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/threshold_watch.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/threshold_watch/threshold_watch.test.js (99%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/watch.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch/watch.test.js (98%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_errors/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_errors/watch_errors.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_errors/watch_errors.test.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_history_item/__tests__/watch_history_item.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_history_item/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_history_item/watch_history_item.js (97%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_status/__tests__/watch_status.js (99%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_status/index.js (100%) rename x-pack/legacy/plugins/watcher/server/{ => np_ready}/models/watch_status/watch_status.js (98%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/indices/index.js => np_ready/routes/api/indices/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/indices/register_indices_routes.js => np_ready/routes/api/indices/register_indices_routes.ts} (62%) rename x-pack/legacy/plugins/watcher/server/{routes/api/license/index.js => np_ready/routes/api/license/index.ts} (100%) rename x-pack/legacy/plugins/watcher/server/{routes/api/license/register_license_routes.js => np_ready/routes/api/license/register_license_routes.ts} (62%) rename x-pack/legacy/plugins/watcher/server/{routes/api/license/register_refresh_route.js => np_ready/routes/api/license/register_refresh_route.ts} (50%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/settings/index.js => np_ready/routes/api/settings/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/settings/register_settings_routes.js => np_ready/routes/api/settings/register_settings_routes.ts} (62%) rename x-pack/legacy/plugins/watcher/server/{routes/api/watch/action/index.js => np_ready/routes/api/watch/action/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/watch/action/register_action_routes.js => np_ready/routes/api/watch/action/register_action_routes.ts} (61%) rename x-pack/legacy/plugins/watcher/server/{routes/api/watch/index.js => np_ready/routes/api/watch/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/watch/register_watch_routes.js => np_ready/routes/api/watch/register_watch_routes.ts} (62%) rename x-pack/legacy/plugins/watcher/server/{routes/api/watches/index.js => np_ready/routes/api/watches/index.ts} (100%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts rename x-pack/legacy/plugins/watcher/server/{routes/api/watches/register_watches_routes.js => np_ready/routes/api/watches/register_watches_routes.ts} (62%) create mode 100644 x-pack/legacy/plugins/watcher/server/np_ready/types.ts delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/history/index.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js delete mode 100644 x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js diff --git a/.eslintrc.js b/.eslintrc.js index 106724c323d30..e01632815bc68 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -209,13 +209,6 @@ module.exports = { 'react-hooks/rules-of-hooks': 'off', }, }, - { - files: ['x-pack/legacy/plugins/watcher/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/rules-of-hooks': 'off', - 'react-hooks/exhaustive-deps': 'off', - }, - }, /** * Prettier diff --git a/src/legacy/ui/public/time_buckets/index.d.ts b/src/legacy/ui/public/time_buckets/index.d.ts new file mode 100644 index 0000000000000..70b9495b81f0e --- /dev/null +++ b/src/legacy/ui/public/time_buckets/index.d.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. + */ + +declare module 'ui/time_buckets' { + export const TimeBuckets: any; +} diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts index 48c7904661e51..5a3f28ed76486 100644 --- a/src/plugins/es_ui_shared/public/request/np_ready_request.ts +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -19,11 +19,12 @@ import { useEffect, useState, useRef } from 'react'; -import { HttpServiceBase } from '../../../../../src/core/public'; +import { HttpServiceBase, HttpFetchQuery } from '../../../../../src/core/public'; export interface SendRequestConfig { path: string; method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; + query?: HttpFetchQuery; body?: any; } @@ -48,10 +49,10 @@ export interface UseRequestResponse { export const sendRequest = async ( httpClient: HttpServiceBase, - { path, method, body }: SendRequestConfig + { path, method, body, query }: SendRequestConfig ): Promise => { try { - const response = await httpClient[method](path, { body }); + const response = await httpClient[method](path, { body, query }); return { data: response.data ? response.data : response, @@ -70,6 +71,7 @@ export const useRequest = ( { path, method, + query, body, pollIntervalMs, initialData, @@ -112,6 +114,7 @@ export const useRequest = ( const requestBody = { path, method, + query, body, }; diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index 199232262773d..f8d07668d0aae 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -20,7 +20,8 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, - '^plugins/watcher/models/(.*)': `${xPackKibanaDirectory}/legacy/plugins/watcher/public/models/$1`, + '^plugins/watcher/np_ready/application/models/(.*)': + `${xPackKibanaDirectory}/legacy/plugins/watcher/public/np_ready/application/models/$1`, '^plugins/([^/.]*)(.*)': `${kibanaDirectory}/src/legacy/core_plugins/$1/public$2`, '^legacy/plugins/xpack_main/(.*);': `${xPackKibanaDirectory}/legacy/plugins/xpack_main/public/$1`, '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': fileMockPath, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx new file mode 100644 index 0000000000000..de285ee15b59d --- /dev/null +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/app_context.mock.tsx @@ -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 React from 'react'; +import { ComponentType } from 'enzyme'; +import { + chromeServiceMock, + docLinksServiceMock, + uiSettingsServiceMock, + notificationServiceMock, + httpServiceMock, +} from '../../../../../../../src/core/public/mocks'; +import { AppContextProvider } from '../../../public/np_ready/application/app_context'; + +export const mockContextValue = { + docLinks: docLinksServiceMock.createStartContract(), + chrome: chromeServiceMock.createStartContract(), + legacy: { + TimeBuckets: class MockTimeBuckets { + setBounds(_domain: any) { + return {}; + } + getInterval() { + return { + expression: {}, + }; + } + }, + MANAGEMENT_BREADCRUMB: { text: 'test' }, + licenseStatus: {}, + }, + uiSettings: uiSettingsServiceMock.createSetupContract(), + toasts: notificationServiceMock.createSetupContract().toasts, + euiUtils: { + useChartsTheme: jest.fn(), + }, + // For our test harness, we don't use this mocked out http service + http: httpServiceMock.createSetupContract(), +}; + +export const withAppContext = (Component: ComponentType) => (props: any) => { + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts similarity index 56% rename from x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js rename to x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts index 8474f8a614bfb..3b3df5fd6f879 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/index.js +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/body_response.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerFieldsRoutes } from './register_fields_routes'; +export const wrapBodyResponse = (obj: object) => JSON.stringify({ body: JSON.stringify(obj) }); + +export const unwrapBodyResponse = (string: string) => JSON.parse(JSON.parse(string).body); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts index 2170559dace5a..7d9c1e4163d7b 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/http_requests.ts @@ -34,7 +34,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { const defaultResponse = { watchHistoryItems: [] }; server.respondWith( 'GET', - `${API_ROOT}/watch/:id/history?startTime=*`, + `${API_ROOT}/watch/:id/history`, mockResponse(defaultResponse, response) ); }; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts index ad005078db0a8..814028fe599ff 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/index.ts @@ -11,7 +11,7 @@ import { setup as watchCreateThresholdSetup } from './watch_create_threshold.hel import { setup as watchEditSetup } from './watch_edit.helpers'; export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../../../../test_utils'; - +export { wrapBodyResponse, unwrapBodyResponse } from './body_response'; export { setupEnvironment } from './setup_environment'; export const pageHelpers = { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts index 806840a7821fd..7e748073c1c6b 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/setup_environment.ts @@ -7,9 +7,17 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { init as initHttpRequests } from './http_requests'; -import { setHttpClient, setSavedObjectsClient } from '../../../public/lib/api'; +import { setHttpClient, setSavedObjectsClient } from '../../../public/np_ready/application/lib/api'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); +mockHttpClient.interceptors.response.use( + res => { + return res.data; + }, + rej => { + return Promise.reject(rej); + } +); const mockSavedObjectsClient = () => { return { @@ -23,7 +31,7 @@ export const setupEnvironment = () => { // @ts-ignore setHttpClient(mockHttpClient); - setSavedObjectsClient(mockSavedObjectsClient()); + setSavedObjectsClient(mockSavedObjectsClient() as any); return { server, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts index bea215281a4bc..dafcf3a7070d2 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_json.helpers.ts @@ -3,10 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { withAppContext } from './app_context.mock'; import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -17,7 +18,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchCreateJsonTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index e33ae02036224..8cebe8ce26229 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES, WATCH_TYPES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -17,7 +18,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchCreateThresholdTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts index d0b458e30c70e..187f4dcaa0a76 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_edit.helpers.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { registerTestBed, TestBed, TestBedConfig } from '../../../../../../test_utils'; -import { WatchEdit } from '../../../public/sections/watch_edit/components/watch_edit'; +import { WatchEdit } from '../../../public/np_ready/application/sections/watch_edit/components/watch_edit'; import { ROUTES } from '../../../common/constants'; -import { registerRouter } from '../../../public/lib/navigation'; +import { registerRouter } from '../../../public/np_ready/application/lib/navigation'; import { WATCH_ID } from './constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -18,7 +19,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchEdit, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchEdit), testBedConfig); export interface WatchEditTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts index 0d3ecaa7a2b9a..e33327ea42ffe 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_list.helpers.ts @@ -13,8 +13,9 @@ import { TestBedConfig, nextTick, } from '../../../../../../test_utils'; -import { WatchList } from '../../../public/sections/watch_list/components/watch_list'; +import { WatchList } from '../../../public/np_ready/application/sections/watch_list/components/watch_list'; import { ROUTES } from '../../../common/constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -23,7 +24,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchList, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchList), testBedConfig); export interface WatchListTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts index 22d57f255ebe6..e7bffe8924e31 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_status.helpers.ts @@ -13,9 +13,10 @@ import { TestBedConfig, nextTick, } from '../../../../../../test_utils'; -import { WatchStatus } from '../../../public/sections/watch_status/components/watch_status'; +import { WatchStatus } from '../../../public/np_ready/application/sections/watch_status/components/watch_status'; import { ROUTES } from '../../../common/constants'; import { WATCH_ID } from './constants'; +import { withAppContext } from './app_context.mock'; const testBedConfig: TestBedConfig = { memoryRouter: { @@ -25,7 +26,7 @@ const testBedConfig: TestBedConfig = { doMountAsync: true, }; -const initTestBed = registerTestBed(WatchStatus, testBedConfig); +const initTestBed = registerTestBed(withAppContext(WatchStatus), testBedConfig); export interface WatchStatusTestBed extends TestBed { actions: { diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts index f45dbe156723b..4c893978ee5cb 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_json.test.ts @@ -4,22 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { act } from 'react-dom/test-utils'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchCreateJsonTestBed } from './helpers/watch_create_json.helpers'; import { WATCH } from './helpers/constants'; -import defaultWatchJson from '../../public/models/watch/default_watch.json'; +import defaultWatchJson from '../../public/np_ready/application/models/watch/default_watch.json'; import { getExecuteDetails } from '../../test/fixtures'; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchCreateJson; -describe.skip(' create route', () => { +describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateJsonTestBed; @@ -107,7 +100,7 @@ describe.skip(' create route', () => { 'There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10.'; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id: watch.id, name: watch.name, type: watch.type, @@ -194,7 +187,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes, }), @@ -258,7 +251,7 @@ describe.skip(' create route', () => { const scheduledTime = `now+${SCHEDULED_TIME}s`; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ triggerData: { triggeredTime, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index 62cfd92182091..36a5c150eead7 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -7,7 +7,13 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { + setupEnvironment, + pageHelpers, + nextTick, + wrapBodyResponse, + unwrapBodyResponse, +} from './helpers'; import { WatchCreateThresholdTestBed } from './helpers/watch_create_threshold.helpers'; import { getExecuteDetails } from '../../test/fixtures'; import { WATCH_TYPES } from '../../common/constants'; @@ -42,31 +48,8 @@ const WATCH_VISUALIZE_DATA = { const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', - getUiSettingsClient: () => ({ - get: () => {}, - isDefault: () => true, - }), -})); - -jest.mock('ui/time_buckets', () => { - class MockTimeBuckets { - setBounds(_domain: any) { - return {}; - } - getInterval() { - return { - expression: {}, - }; - } - } - return { TimeBuckets: MockTimeBuckets }; -}); - -jest.mock('../../public/lib/api', () => ({ - ...jest.requireActual('../../public/lib/api'), +jest.mock('../../public/np_ready/application/lib/api', () => ({ + ...jest.requireActual('../../public/np_ready/application/lib/api'), loadIndexPatterns: async () => { const INDEX_PATTERNS = [ { attributes: { title: 'index1' } }, @@ -85,7 +68,7 @@ jest.mock('@elastic/eui', () => ({ EuiComboBox: (props: any) => ( { + onChange={(syntheticEvent: any) => { props.onChange([syntheticEvent['0']]); }} /> @@ -94,7 +77,7 @@ jest.mock('@elastic/eui', () => ({ const { setup } = pageHelpers.watchCreateThreshold; -describe.skip(' create route', () => { +describe(' create route', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchCreateThresholdTestBed; @@ -105,12 +88,9 @@ describe.skip(' create route', () => { describe('on component mount', () => { beforeEach(async () => { testBed = await setup(); - - await act(async () => { - const { component } = testBed; - await nextTick(); - component.update(); - }); + const { component } = testBed; + await nextTick(); + component.update(); }); test('should set the correct page title', () => { @@ -125,13 +105,6 @@ describe.skip(' create route', () => { httpRequestsMockHelpers.setLoadEsFieldsResponse({ fields: ES_FIELDS }); httpRequestsMockHelpers.setLoadSettingsResponse(SETTINGS); httpRequestsMockHelpers.setLoadWatchVisualizeResponse(WATCH_VISUALIZE_DATA); - - testBed = await setup(); - - await act(async () => { - await nextTick(); - testBed.component.update(); - }); }); describe('form validation', () => { @@ -173,7 +146,7 @@ describe.skip(' create route', () => { expect(find('saveWatchButton').props().disabled).toEqual(true); }); - test('it should enable the Create button and render additonal content with valid fields', async () => { + test('it should enable the Create button and render additional content with valid fields', async () => { const { form, find, component, exists } = testBed; form.setInputValue('nameInput', 'my_test_watch'); @@ -192,39 +165,30 @@ describe.skip(' create route', () => { expect(exists('watchActionsPanel')).toBe(true); }); - describe('watch conditions', () => { - beforeEach(async () => { - const { form, find, component } = testBed; + // Looks like there is an issue with using 'mockComboBox'. + describe.skip('watch conditions', () => { + beforeEach(() => { + const { form, find } = testBed; // Name, index and time fields are required before the watch condition expression renders form.setInputValue('nameInput', 'my_test_watch'); - find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox - form.setInputValue('watchTimeFieldSelect', '@timestamp'); - - await act(async () => { - await nextTick(); - component.update(); + act(() => { + find('mockComboBox').simulate('change', [{ label: 'index1', value: 'index1' }]); // Using mocked EuiComboBox }); + form.setInputValue('watchTimeFieldSelect', '@timestamp'); }); - test('should require a threshold value', async () => { - const { form, find, component } = testBed; - - find('watchThresholdButton').simulate('click'); + test('should require a threshold value', () => { + const { form, find } = testBed; - // Provide invalid value - form.setInputValue('watchThresholdInput', ''); - - expect(form.getErrorsMessages()).toContain('A value is required.'); - - // Provide valid value - form.setInputValue('watchThresholdInput', '0'); - - await act(async () => { - await nextTick(); - component.update(); + act(() => { + find('watchThresholdButton').simulate('click'); + // Provide invalid value + form.setInputValue('watchThresholdInput', ''); + // Provide valid value + form.setInputValue('watchThresholdInput', '0'); }); - + expect(form.getErrorsMessages()).toContain('A value is required.'); expect(form.getErrorsMessages().length).toEqual(0); }); }); @@ -273,7 +237,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -300,7 +264,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { logging_1: 'force_execute', @@ -341,7 +305,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -367,7 +331,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { index_1: 'force_execute', @@ -401,7 +365,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -430,7 +394,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { slack_1: 'force_execute', @@ -471,7 +435,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -504,7 +468,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { email_1: 'force_execute', @@ -559,7 +523,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -594,7 +558,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { webhook_1: 'force_execute', @@ -645,7 +609,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -682,7 +646,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { jira_1: 'force_execute', @@ -723,7 +687,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).watch.id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).watch.id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -750,7 +714,7 @@ describe.skip(' create route', () => { }; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ executeDetails: getExecuteDetails({ actionModes: { pagerduty_1: 'force_execute', @@ -784,7 +748,7 @@ describe.skip(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; const thresholdWatch = { - id: JSON.parse(latestRequest.requestBody).id, // watch ID is created dynamically + id: unwrapBodyResponse(latestRequest.requestBody).id, // watch ID is created dynamically name: WATCH_NAME, type: WATCH_TYPES.THRESHOLD, isNew: true, @@ -801,7 +765,7 @@ describe.skip(' create route', () => { threshold: 1000, }; - expect(latestRequest.requestBody).toEqual(JSON.stringify(thresholdWatch)); + expect(latestRequest.requestBody).toEqual(wrapBodyResponse(thresholdWatch)); }); }); }); diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts index fb9ad934249e9..1eee3d3b7e6ee 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts @@ -6,36 +6,17 @@ import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchEditTestBed } from './helpers/watch_edit.helpers'; import { WATCH } from './helpers/constants'; -import defaultWatchJson from '../../public/models/watch/default_watch.json'; +import defaultWatchJson from '../../public/np_ready/application/models/watch/default_watch.json'; import { getWatch } from '../../test/fixtures'; import { getRandomString } from '../../../../../test_utils'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => { - class MockTimeBuckets { - setBounds(_domain: any) { - return {}; - } - getInterval() { - return { - expression: {}, - }; - } - } - return { TimeBuckets: MockTimeBuckets }; -}); - -jest.mock('../../public/lib/api', () => ({ - ...jest.requireActual('../../public/lib/api'), +jest.mock('../../public/np_ready/application/lib/api', () => ({ + ...jest.requireActual('../../public/np_ready/application/lib/api'), loadIndexPatterns: async () => { const INDEX_PATTERNS = [ { attributes: { title: 'index1' } }, @@ -49,7 +30,7 @@ jest.mock('../../public/lib/api', () => ({ const { setup } = pageHelpers.watchEdit; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchEditTestBed; @@ -110,7 +91,7 @@ describe.skip('', () => { 'There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10.'; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id: watch.id, name: EDITED_WATCH_NAME, type: watch.type, @@ -202,7 +183,7 @@ describe.skip('', () => { } = watch; expect(latestRequest.requestBody).toEqual( - JSON.stringify({ + wrapBodyResponse({ id, name: EDITED_WATCH_NAME, type, diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts index bc2eadb7d9be9..a0327c6dfa1db 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -18,16 +18,9 @@ import { ROUTES } from '../../common/constants'; const { API_ROOT } = ROUTES; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchList; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchListTestBed; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts index e12acd2e32ccf..973c14893f342 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_status.test.ts @@ -14,13 +14,6 @@ import { WATCH_STATES, ACTION_STATES } from '../../common/constants'; const { API_ROOT } = ROUTES; -jest.mock('ui/chrome', () => ({ - breadcrumbs: { set: () => {} }, - addBasePath: (path: string) => path || '/api/watcher', -})); - -jest.mock('ui/time_buckets', () => {}); - const { setup } = pageHelpers.watchStatus; const watchHistory1 = getWatchHistory({ startTime: '2019-06-04T01:11:11.294' }); @@ -45,7 +38,7 @@ const watch = { }, }; -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: WatchStatusTestBed; diff --git a/x-pack/legacy/plugins/watcher/kibana.json b/x-pack/legacy/plugins/watcher/kibana.json new file mode 100644 index 0000000000000..ccec8a1b77683 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "watcher", + "version": "kibana", + "requiredPlugins": [ + "home" + ], + "server": true, + "ui": true +} diff --git a/x-pack/legacy/plugins/watcher/plugin_definition.js b/x-pack/legacy/plugins/watcher/plugin_definition.js deleted file mode 100644 index 4a5946cc4974d..0000000000000 --- a/x-pack/legacy/plugins/watcher/plugin_definition.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { i18n } from '@kbn/i18n'; -import { registerFieldsRoutes } from './server/routes/api/fields'; -import { registerSettingsRoutes } from './server/routes/api/settings'; -import { registerHistoryRoutes } from './server/routes/api/history'; -import { registerIndicesRoutes } from './server/routes/api/indices'; -import { registerLicenseRoutes } from './server/routes/api/license'; -import { registerWatchesRoutes } from './server/routes/api/watches'; -import { registerWatchRoutes } from './server/routes/api/watch'; -import { registerLicenseChecker } from '../../server/lib/register_license_checker'; -import { PLUGIN } from './common/constants'; - -export const pluginDefinition = { - id: PLUGIN.ID, - configPrefix: 'xpack.watcher', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: ['plugins/watcher'], - home: ['plugins/watcher/register_feature'], - }, - init: function (server) { - // Register license checker - registerLicenseChecker( - server, - PLUGIN.ID, - PLUGIN.getI18nName(i18n), - PLUGIN.MINIMUM_LICENSE_REQUIRED - ); - - registerFieldsRoutes(server); - registerHistoryRoutes(server); - registerIndicesRoutes(server); - registerLicenseRoutes(server); - registerSettingsRoutes(server); - registerWatchesRoutes(server); - registerWatchRoutes(server); - }, -}; diff --git a/x-pack/legacy/plugins/watcher/plugin_definition.ts b/x-pack/legacy/plugins/watcher/plugin_definition.ts new file mode 100644 index 0000000000000..2da05253fdb32 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/plugin_definition.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 './server/np_ready'; +import { PLUGIN } from './common/constants'; + +export const pluginDefinition = { + id: PLUGIN.ID, + configPrefix: 'xpack.watcher', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch', 'xpack_main'], + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/np_ready/application/index.scss'), + managementSections: ['plugins/watcher/legacy'], + home: ['plugins/watcher/register_feature'], + }, + init(server: any) { + plugin({} as any).setup(server.newPlatform.setup.core, { + __LEGACY: { + route: server.route.bind(server), + plugins: { + watcher: server.plugins[PLUGIN.ID], + xpack_main: server.plugins.xpack_main, + }, + }, + }); + }, +}; diff --git a/x-pack/legacy/plugins/watcher/public/app.html b/x-pack/legacy/plugins/watcher/public/app.html deleted file mode 100644 index 8c7c3eb946aef..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/app.html +++ /dev/null @@ -1,3 +0,0 @@ - -
    -
    \ No newline at end of file diff --git a/x-pack/legacy/plugins/watcher/public/legacy.ts b/x-pack/legacy/plugins/watcher/public/legacy.ts new file mode 100644 index 0000000000000..d7b85ccfeb7b4 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/legacy.ts @@ -0,0 +1,146 @@ +/* + * 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, App, AppUnmount } from 'src/core/public'; +import { i18n } from '@kbn/i18n'; + +/* Legacy UI imports */ +import { npSetup, npStart } from 'ui/new_platform'; +import routes from 'ui/routes'; +import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; +import { TimeBuckets } from 'ui/time_buckets'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +/* Legacy UI imports */ + +import { plugin } from './np_ready'; +import { PLUGIN } from '../common/constants'; +import { LICENSE_STATUS_INVALID, LICENSE_STATUS_UNAVAILABLE } from '../../../common/constants'; +import { manageAngularLifecycle } from './manage_angular_lifecycle'; + +const template = ` +
    +
    `; + +let elem: HTMLElement; +let mountApp: () => AppUnmount | Promise; +let unmountApp: AppUnmount | Promise; +routes.when('/management/elasticsearch/watcher/:param1?/:param2?/:param3?/:param4?', { + template, + controller: class WatcherController { + constructor($injector: any, $scope: any) { + const $route = $injector.get('$route'); + const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); + const shimCore: CoreSetup = { + ...npSetup.core, + application: { + ...npSetup.core.application, + register(app: App): void { + mountApp = () => + app.mount(npStart as any, { + element: elem, + appBasePath: '/management/elasticsearch/watcher/', + }); + }, + }, + }; + + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + if (elem) { + ((unmountApp as unknown) as AppUnmount)(); + } + + $scope.$$postDigest(() => { + elem = document.getElementById('watchReactRoot')!; + const instance = plugin(); + instance.setup(shimCore, { + ...(npSetup.plugins as typeof npSetup.plugins & { eui_utils: any }), + __LEGACY: { + MANAGEMENT_BREADCRUMB, + TimeBuckets, + licenseStatus, + }, + }); + + instance.start(npStart.core, npStart.plugins); + + (mountApp() as Promise).then(fn => (unmountApp = fn)); + + manageAngularLifecycle($scope, $route, elem); + }); + } + } as any, + // @ts-ignore + controllerAs: 'watchRoute', +}); + +routes.defaults(/\/management/, { + resolve: { + watcherManagementSection: () => { + const watchesSection = management.getSection('elasticsearch/watcher'); + const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); + const { status } = licenseStatus; + + if (status === LICENSE_STATUS_INVALID || status === LICENSE_STATUS_UNAVAILABLE) { + return watchesSection.hide(); + } + + watchesSection.show(); + }, + }, +}); + +management.getSection('elasticsearch').register('watcher', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { + defaultMessage: 'Watcher', + }), + order: 6, + url: '#/management/elasticsearch/watcher/', +} as any); + +management.getSection('elasticsearch/watcher').register('watches', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watchesDisplayName', { + defaultMessage: 'Watches', + }), + order: 1, +} as any); + +management.getSection('elasticsearch/watcher').register('watch', { + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('status', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.statusDisplayName', { + defaultMessage: 'Status', + }), + order: 1, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('edit', { + display: i18n.translate('xpack.watcher.sections.watchList.managementSection.editDisplayName', { + defaultMessage: 'Edit', + }), + order: 2, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('new', { + display: i18n.translate( + 'xpack.watcher.sections.watchList.managementSection.newWatchDisplayName', + { + defaultMessage: 'New Watch', + } + ), + order: 1, + visible: false, +} as any); + +management.getSection('elasticsearch/watcher/watch').register('history-item', { + order: 1, + visible: false, +} as any); diff --git a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts b/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts deleted file mode 100644 index 88f23465d33e8..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/documentation_links.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { ACTION_TYPES } from '../../../common/constants'; - -const elasticDocLinkBase = `${ELASTIC_WEBSITE_URL}guide/en/`; - -const esBase = `${elasticDocLinkBase}elasticsearch/reference/${DOC_LINK_VERSION}`; -const esStackBase = `${elasticDocLinkBase}elastic-stack-overview/${DOC_LINK_VERSION}`; -const kibanaBase = `${elasticDocLinkBase}kibana/${DOC_LINK_VERSION}`; - -export const putWatchApiUrl = `${esBase}/watcher-api-put-watch.html`; -export const executeWatchApiUrl = `${esBase}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`; -export const watcherGettingStartedUrl = `${kibanaBase}/watcher-ui.html`; - -export const watchActionsConfigurationMap = { - [ACTION_TYPES.SLACK]: `${esStackBase}/actions-slack.html#configuring-slack`, - [ACTION_TYPES.PAGERDUTY]: `${esStackBase}/actions-pagerduty.html#configuring-pagerduty`, - [ACTION_TYPES.JIRA]: `${esStackBase}/actions-jira.html#configuring-jira`, -}; diff --git a/x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js b/x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts similarity index 75% rename from x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js rename to x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts index 3813e632a0a73..efd40eaf83daa 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/manage_angular_lifecycle.js +++ b/x-pack/legacy/plugins/watcher/public/manage_angular_lifecycle.ts @@ -6,7 +6,7 @@ import { unmountComponentAtNode } from 'react-dom'; -export const manageAngularLifecycle = ($scope, $route, elem) => { +export const manageAngularLifecycle = ($scope: any, $route: any, elem: HTMLElement) => { const lastRoute = $route.current; const deregister = $scope.$on('$locationChangeSuccess', () => { @@ -17,7 +17,12 @@ export const manageAngularLifecycle = ($scope, $route, elem) => { }); $scope.$on('$destroy', () => { - deregister && deregister(); - elem && unmountComponentAtNode(elem); + if (deregister) { + deregister(); + } + + if (elem) { + unmountComponentAtNode(elem); + } }); }; diff --git a/x-pack/legacy/plugins/watcher/public/models/index.d.ts b/x-pack/legacy/plugins/watcher/public/models/index.d.ts deleted file mode 100644 index d96d8d192e166..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/models/index.d.ts +++ /dev/null @@ -1,42 +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. - */ -declare module 'plugins/watcher/models/visualize_options' { - export const VisualizeOptions: any; -} - -declare module 'plugins/watcher/models/watch' { - export const Watch: any; -} - -declare module 'plugins/watcher/models/watch/threshold_watch' { - export const ThresholdWatch: any; -} - -declare module 'plugins/watcher/models/watch/json_watch' { - export const JsonWatch: any; -} - -declare module 'plugins/watcher/models/execute_details/execute_details' { - export const ExecuteDetails: any; -} - -declare module 'plugins/watcher/models/watch_history_item' { - export const WatchHistoryItem: any; -} - -declare module 'plugins/watcher/models/watch_status' { - export const WatchStatus: any; -} - -declare module 'plugins/watcher/models/settings' { - export const Settings: any; -} -declare module 'plugins/watcher/models/action' { - export const Action: any; -} -declare module 'ui/time_buckets' { - export const TimeBuckets: any; -} diff --git a/x-pack/legacy/plugins/watcher/public/app.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx similarity index 60% rename from x-pack/legacy/plugins/watcher/public/app.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx index b206348547966..36fa1cce9d6dd 100644 --- a/x-pack/legacy/plugins/watcher/public/app.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/app.tsx @@ -4,54 +4,61 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import React from 'react'; +import { + ChromeStart, + DocLinksStart, + HttpSetup, + ToastsSetup, + IUiSettingsClient, +} from 'src/core/public'; + +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { + HashRouter, + Switch, + Route, + Redirect, + withRouter, + RouteComponentProps, +} from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { WatchStatus } from './sections/watch_status/components/watch_status'; import { WatchEdit } from './sections/watch_edit/components/watch_edit'; import { WatchList } from './sections/watch_list/components/watch_list'; import { registerRouter } from './lib/navigation'; import { BASE_PATH } from './constants'; -import { LICENSE_STATUS_VALID } from '../../../common/constants'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { LICENSE_STATUS_VALID } from '../../../../../common/constants'; +import { AppContextProvider } from './app_context'; +import { LegacyDependencies } from '../types'; -class ShareRouter extends Component { - static contextTypes = { - router: PropTypes.shape({ - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired - }).isRequired - }).isRequired - } - constructor(...args) { - super(...args); - this.registerRouter(); - } +const ShareRouter = withRouter(({ children, history }: RouteComponentProps & { children: any }) => { + registerRouter({ history }); + return children; +}); - registerRouter() { - // Share the router with the app without requiring React or context. - const { router } = this.context; - registerRouter(router); - } - - render() { - return this.props.children; - } +export interface AppDeps { + chrome: ChromeStart; + docLinks: DocLinksStart; + toasts: ToastsSetup; + http: HttpSetup; + uiSettings: IUiSettingsClient; + legacy: LegacyDependencies; + euiUtils: any; } -export const App = ({ licenseStatus }) => { - const { status, message } = licenseStatus; + +export const App = (deps: AppDeps) => { + const { status, message } = deps.legacy.licenseStatus; if (status !== LICENSE_STATUS_VALID) { return ( - )} + } color="warning" iconType="help" > @@ -69,7 +76,9 @@ export const App = ({ licenseStatus }) => { return ( - + + + ); @@ -81,7 +90,11 @@ export const AppWithoutRouter = () => ( - + ); diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx new file mode 100644 index 0000000000000..5696ab3cb91ba --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/app_context.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useContext } from 'react'; +import { DocLinksStart } from 'src/core/public'; +import { ACTION_TYPES } from '../../../common/constants'; +import { AppDeps } from './app'; + +interface ContextValue extends Omit { + links: ReturnType; +} + +const AppContext = createContext(null as any); + +const generateDocLinks = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => { + const elasticDocLinkBase = `${ELASTIC_WEBSITE_URL}guide/en/`; + const esBase = `${elasticDocLinkBase}elasticsearch/reference/${DOC_LINK_VERSION}`; + const kibanaBase = `${elasticDocLinkBase}kibana/${DOC_LINK_VERSION}`; + const putWatchApiUrl = `${esBase}/watcher-api-put-watch.html`; + const executeWatchApiUrl = `${esBase}/watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`; + const watcherGettingStartedUrl = `${kibanaBase}/watcher-ui.html`; + const watchActionsConfigurationMap = { + [ACTION_TYPES.SLACK]: `${esBase}/actions-slack.html#configuring-slack`, + [ACTION_TYPES.PAGERDUTY]: `${esBase}/actions-pagerduty.html#configuring-pagerduty`, + [ACTION_TYPES.JIRA]: `${esBase}/actions-jira.html#configuring-jira`, + }; + + return { + putWatchApiUrl, + executeWatchApiUrl, + watcherGettingStartedUrl, + watchActionsConfigurationMap, + }; +}; + +export const AppContextProvider = ({ + children, + value, +}: { + value: AppDeps; + children: React.ReactNode; +}) => { + const { docLinks, ...rest } = value; + return ( + + {children} + + ); +}; + +export const useAppContext = () => { + const ctx = useContext(AppContext); + if (!ctx) { + throw new Error('"useAppContext" can only be called inside of AppContext.Provider!'); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx new file mode 100644 index 0000000000000..3f2a10f004649 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/boot.tsx @@ -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 React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { SavedObjectsClientContract } from 'src/core/public'; + +import { App, AppDeps } from './app'; +import { setHttpClient, setSavedObjectsClient } from './lib/api'; +import { LegacyDependencies } from '../types'; + +interface BootDeps extends AppDeps { + element: HTMLElement; + savedObjects: SavedObjectsClientContract; + I18nContext: any; + legacy: LegacyDependencies; +} + +export const boot = (bootDeps: BootDeps) => { + const { I18nContext, element, legacy, savedObjects, ...appDeps } = bootDeps; + + setHttpClient(appDeps.http); + setSavedObjectsClient(savedObjects); + + render( + + + , + element + ); + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/watcher/public/components/confirm_watches_modal.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/confirm_watches_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/confirm_watches_modal.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/confirm_watches_modal.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx index 6d75495cbfc20..363185f3457d8 100644 --- a/x-pack/legacy/plugins/watcher/public/components/delete_watches_modal.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/delete_watches_modal.tsx @@ -6,8 +6,8 @@ import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { toastNotifications } from 'ui/notify'; import { deleteWatches } from '../lib/api'; +import { useAppContext } from '../app_context'; export const DeleteWatchesModal = ({ watchesToDelete, @@ -16,6 +16,7 @@ export const DeleteWatchesModal = ({ watchesToDelete: string[]; callback: (deleted?: string[]) => void; }) => { + const { toasts } = useAppContext(); const numWatchesToDelete = watchesToDelete.length; if (!numWatchesToDelete) { return null; @@ -54,7 +55,7 @@ export const DeleteWatchesModal = ({ const numErrors = errors.length; callback(successes); if (numSuccesses > 0) { - toastNotifications.addSuccess( + toasts.addSuccess( i18n.translate( 'xpack.watcher.sections.watchList.deleteSelectedWatchesSuccessNotification.descriptionText', { @@ -67,7 +68,7 @@ export const DeleteWatchesModal = ({ } if (numErrors > 0) { - toastNotifications.addDanger( + toasts.addDanger( i18n.translate( 'xpack.watcher.sections.watchList.deleteSelectedWatchesErrorNotification.descriptionText', { diff --git a/x-pack/legacy/plugins/watcher/public/components/form_errors.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/form_errors.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/form_errors.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/form_errors.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error_forbidden.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_forbidden.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error_forbidden.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_forbidden.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/page_error/page_error_not_exist.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_not_exist.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/page_error/page_error_not_exist.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/page_error/page_error_not_exist.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/section_error.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx similarity index 80% rename from x-pack/legacy/plugins/watcher/public/components/section_error.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx index 8951b95b75078..1c77cf2b49ae2 100644 --- a/x-pack/legacy/plugins/watcher/public/components/section_error.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_error.tsx @@ -8,6 +8,18 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React, { Fragment } from 'react'; export interface Error { + error: string; + + /** + * wrapEsError() on the server adds a "cause" array + */ + cause?: string[]; + + message?: string; + + /** + * @deprecated + */ data: { error: string; cause?: string[]; @@ -21,11 +33,9 @@ interface Props { } export const SectionError: React.FunctionComponent = ({ title, error, ...rest }) => { - const { - error: errorString, - cause, // wrapEsError() on the server adds a "cause" array - message, - } = error.data; + const data = error.data || error; + + const { error: errorString, cause, message } = data; return ( diff --git a/x-pack/legacy/plugins/watcher/public/components/section_loading.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_loading.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/components/section_loading.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/section_loading.tsx diff --git a/x-pack/legacy/plugins/watcher/public/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/components/watch_status.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx index 39e6a5247b4a6..8afd174f8561e 100644 --- a/x-pack/legacy/plugins/watcher/public/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { ACTION_STATES, WATCH_STATES } from '../../common/constants'; +import { ACTION_STATES, WATCH_STATES } from '../../../../common/constants'; function StatusIcon({ status }: { status: string }) { switch (status) { diff --git a/x-pack/legacy/plugins/watcher/public/constants/base_path.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/constants/base_path.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/constants/base_path.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/constants/base_path.ts diff --git a/x-pack/legacy/plugins/watcher/public/constants/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/constants/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/constants/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/index.scss b/x-pack/legacy/plugins/watcher/public/np_ready/application/index.scss similarity index 100% rename from x-pack/legacy/plugins/watcher/public/index.scss rename to x-pack/legacy/plugins/watcher/public/np_ready/application/index.scss diff --git a/x-pack/legacy/plugins/watcher/public/lib/api.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/public/lib/api.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts index d5c430f9244c4..c08545904e351 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/api.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/api.ts @@ -3,20 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Settings } from 'plugins/watcher/models/settings'; -import { Watch } from 'plugins/watcher/models/watch'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { WatchStatus } from 'plugins/watcher/models/watch_status'; - -import { __await } from 'tslib'; -import chrome from 'ui/chrome'; -import { ROUTES } from '../../common/constants'; -import { BaseWatch, ExecutedWatchDetails } from '../../common/types/watch_types'; +import { HttpSetup, SavedObjectsClientContract } from 'src/core/public'; +import { Settings } from 'plugins/watcher/np_ready/application/models/settings'; +import { Watch } from 'plugins/watcher/np_ready/application/models/watch'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { WatchStatus } from 'plugins/watcher/np_ready/application/models/watch_status'; + +import { BaseWatch, ExecutedWatchDetails } from '../../../../common/types/watch_types'; import { useRequest, sendRequest } from './use_request'; -let httpClient: ng.IHttpService; +import { ROUTES } from '../../../../common/constants'; + +let httpClient: HttpSetup; -export const setHttpClient = (anHttpClient: ng.IHttpService) => { +export const setHttpClient = (anHttpClient: HttpSetup) => { httpClient = anHttpClient; }; @@ -24,19 +24,17 @@ export const getHttpClient = () => { return httpClient; }; -let savedObjectsClient: any; +let savedObjectsClient: SavedObjectsClientContract; -export const setSavedObjectsClient = (aSavedObjectsClient: any) => { +export const setSavedObjectsClient = (aSavedObjectsClient: SavedObjectsClientContract) => { savedObjectsClient = aSavedObjectsClient; }; -export const getSavedObjectsClient = () => { - return savedObjectsClient; -}; +export const getSavedObjectsClient = () => savedObjectsClient; -const basePath = chrome.addBasePath(ROUTES.API_ROOT); +const basePath = ROUTES.API_ROOT; -export const loadWatches = (pollIntervalMs: number) => { +export const useLoadWatches = (pollIntervalMs: number) => { return useRequest({ path: `${basePath}/watches`, method: 'get', @@ -47,7 +45,7 @@ export const loadWatches = (pollIntervalMs: number) => { }); }; -export const loadWatchDetail = (id: string) => { +export const useLoadWatchDetail = (id: string) => { return useRequest({ path: `${basePath}/watch/${id}`, method: 'get', @@ -55,15 +53,10 @@ export const loadWatchDetail = (id: string) => { }); }; -export const loadWatchHistory = (id: string, startTime: string) => { - let path = `${basePath}/watch/${id}/history`; - - if (startTime) { - path += `?startTime=${startTime}`; - } - +export const useLoadWatchHistory = (id: string, startTime: string) => { return useRequest({ - path, + query: startTime ? { startTime } : undefined, + path: `${basePath}/watch/${id}/history`, method: 'get', deserializer: ({ watchHistoryItems = [] }: { watchHistoryItems: any }) => { return watchHistoryItems.map((historyItem: any) => @@ -73,7 +66,7 @@ export const loadWatchHistory = (id: string, startTime: string) => { }); }; -export const loadWatchHistoryDetail = (id: string | undefined) => { +export const useLoadWatchHistoryDetail = (id: string | undefined) => { return useRequest({ path: !id ? '' : `${basePath}/history/${id}`, method: 'get', @@ -83,12 +76,10 @@ export const loadWatchHistoryDetail = (id: string | undefined) => { }; export const deleteWatches = async (watchIds: string[]) => { - const body = { + const body = JSON.stringify({ watchIds, - }; - const { - data: { results }, - } = await getHttpClient().post(`${basePath}/watches/delete`, body); + }); + const { results } = await getHttpClient().post(`${basePath}/watches/delete`, { body }); return results; }; @@ -107,8 +98,8 @@ export const activateWatch = async (id: string) => { }; export const loadWatch = async (id: string) => { - const { data: watch } = await getHttpClient().get(`${basePath}/watch/${id}`); - return Watch.fromUpstreamJson(watch.watch); + const { watch } = await getHttpClient().get(`${basePath}/watch/${id}`); + return Watch.fromUpstreamJson(watch); }; export const getMatchingIndices = async (pattern: string) => { @@ -118,32 +109,32 @@ export const getMatchingIndices = async (pattern: string) => { if (!pattern.endsWith('*')) { pattern = `${pattern}*`; } - const { - data: { indices }, - } = await getHttpClient().post(`${basePath}/indices`, { pattern }); + const body = JSON.stringify({ pattern }); + const { indices } = await getHttpClient().post(`${basePath}/indices`, { body }); return indices; }; export const fetchFields = async (indexes: string[]) => { - const { - data: { fields }, - } = await getHttpClient().post(`${basePath}/fields`, { indexes }); + const { fields } = await getHttpClient().post(`${basePath}/fields`, { + body: JSON.stringify({ indexes }), + }); return fields; }; export const createWatch = async (watch: BaseWatch) => { - const { data } = await getHttpClient().put(`${basePath}/watch/${watch.id}`, watch.upstreamJson); - return data; + return await getHttpClient().put(`${basePath}/watch/${watch.id}`, { + body: JSON.stringify(watch.upstreamJson), + }); }; export const executeWatch = async (executeWatchDetails: ExecutedWatchDetails, watch: BaseWatch) => { return sendRequest({ path: `${basePath}/watch/execute`, method: 'put', - body: { + body: JSON.stringify({ executeDetails: executeWatchDetails.upstreamJson, watch: watch.upstreamJson, - }, + }), }); }; @@ -156,19 +147,19 @@ export const loadIndexPatterns = async () => { return savedObjects; }; -export const getWatchVisualizationData = (watchModel: BaseWatch, visualizeOptions: any) => { +export const useGetWatchVisualizationData = (watchModel: BaseWatch, visualizeOptions: any) => { return useRequest({ path: `${basePath}/watch/visualize`, method: 'post', - body: { + body: JSON.stringify({ watch: watchModel.upstreamJson, options: visualizeOptions.upstreamJson, - }, + }), deserializer: ({ visualizeData }: { visualizeData: any }) => visualizeData, }); }; -export const loadSettings = () => { +export const useLoadSettings = () => { return useRequest({ path: `${basePath}/settings`, method: 'get', @@ -183,11 +174,8 @@ export const loadSettings = () => { }; export const ackWatchAction = async (watchId: string, actionId: string) => { - const { - data: { watchStatus }, - } = await getHttpClient().put( - `${basePath}/watch/${watchId}/action/${actionId}/acknowledge`, - null + const { watchStatus } = await getHttpClient().put( + `${basePath}/watch/${watchId}/action/${actionId}/acknowledge` ); return WatchStatus.fromUpstreamJson(watchStatus); }; diff --git a/x-pack/legacy/plugins/watcher/public/lib/breadcrumbs.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/breadcrumbs.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/breadcrumbs.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/breadcrumbs.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/format_date.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/format_date.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/format_date.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/format_date.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/get_search_value.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_search_value.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/get_search_value.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_search_value.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts similarity index 95% rename from x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts index 35bd19e7007c6..ce3b96ac17def 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/get_time_unit_label.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/get_time_unit_label.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { TIME_UNITS } from '../../common/constants'; +import { TIME_UNITS } from '../../../../common/constants'; export function getTimeUnitLabel(timeUnit = TIME_UNITS.SECOND, timeValue = '0') { switch (timeUnit) { diff --git a/x-pack/legacy/plugins/watcher/public/lib/navigation.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/navigation.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/lib/navigation.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/navigation.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/use_request.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts similarity index 99% rename from x-pack/legacy/plugins/watcher/public/lib/use_request.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts index 4788b655d9e88..572403b14b9df 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/use_request.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/lib/use_request.ts @@ -11,6 +11,7 @@ import { sendRequest as _sendRequest, useRequest as _useRequest, } from '../shared_imports'; + import { getHttpClient } from './api'; export const sendRequest = (config: SendRequestConfig): Promise => { diff --git a/x-pack/legacy/plugins/watcher/public/models/action/action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js similarity index 95% rename from x-pack/legacy/plugins/watcher/public/models/action/action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js index 2f1850c3a434c..4e6ec21703b96 100644 --- a/x-pack/legacy/plugins/watcher/public/models/action/action.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/action.js @@ -5,7 +5,7 @@ */ import { get, set } from 'lodash'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES } from '../../../../../common/constants'; import { EmailAction } from './email_action'; import { LoggingAction } from './logging_action'; import { SlackAction } from './slack_action'; diff --git a/x-pack/legacy/plugins/watcher/public/models/action/base_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/base_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/base_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/base_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/email_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/email_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/email_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/email_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/index_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/index_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/index_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/jira_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/jira_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/jira_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/jira_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/logging_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/logging_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/logging_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/pagerduty_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/pagerduty_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/pagerduty_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/pagerduty_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/slack_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/slack_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/slack_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/slack_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/unknown_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/unknown_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/unknown_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/unknown_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action/webhook_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action/webhook_action.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js diff --git a/x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js similarity index 95% rename from x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js index fa9e056554ab0..b177eb5bb2291 100644 --- a/x-pack/legacy/plugins/watcher/public/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/action_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; export class ActionStatus { constructor(props = {}) { diff --git a/x-pack/legacy/plugins/watcher/public/models/action_status/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/action_status/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/action_status/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/execute_details/execute_details.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/execute_details/execute_details.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/public/models/execute_details/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/execute_details/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/execute_details/index.js diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts new file mode 100644 index 0000000000000..a8ddb6ca2b76d --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/index.d.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +declare module 'plugins/watcher/np_ready/application/models/visualize_options' { + export const VisualizeOptions: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch' { + export const Watch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch/threshold_watch' { + export const ThresholdWatch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch/json_watch' { + export const JsonWatch: any; +} + +declare module 'plugins/watcher/np_ready/application/models/execute_details/execute_details' { + export const ExecuteDetails: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch_history_item' { + export const WatchHistoryItem: any; +} + +declare module 'plugins/watcher/np_ready/application/models/watch_status' { + export const WatchStatus: any; +} + +declare module 'plugins/watcher/np_ready/application/models/settings' { + export const Settings: any; +} +declare module 'plugins/watcher/np_ready/application/models/action' { + export const Action: any; +} diff --git a/x-pack/legacy/plugins/watcher/public/models/settings/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/settings/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/settings/settings.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/settings.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/settings/settings.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/settings/settings.js diff --git a/x-pack/legacy/plugins/watcher/public/models/visualize_options/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/visualize_options/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/visualize_options/visualize_options.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/visualize_options.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/visualize_options/visualize_options.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/visualize_options/visualize_options.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts similarity index 94% rename from x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts index 65ab537889ea4..cefaaa3b1abd3 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/agg_types.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/agg_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AGG_TYPES } from '../../../common/constants'; +import { AGG_TYPES } from '../../../../../common/constants'; export interface AggType { text: string; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/base_watch.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/base_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/base_watch.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts similarity index 96% rename from x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts index b636cdaf14c18..edc3a03c25227 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/comparators.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/comparators.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; -import { COMPARATORS } from '../../../common/constants'; +import { COMPARATORS } from '../../../../../common/constants'; export interface Comparator { text: string; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/default_watch.json b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/default_watch.json similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/default_watch.json rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/default_watch.json diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/group_by_types.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/group_by_types.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/group_by_types.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/group_by_types.ts diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js similarity index 98% rename from x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js index 3dd7af759970e..2e2ee47640cf0 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/json_watch.js @@ -7,7 +7,7 @@ import uuid from 'uuid'; import { get } from 'lodash'; import { BaseWatch } from './base_watch'; -import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../../../common/constants'; import defaultWatchJson from './default_watch.json'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/check_action_id_collision.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/check_action_id_collision.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/check_action_id_collision.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/check_action_id_collision.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/check_action_id_collision/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/check_action_id_collision/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/create_action_id.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/create_action_id.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/create_action_id.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/create_action_id.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch/lib/create_action_id/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/lib/create_action_id/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js similarity index 92% rename from x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js index a0873934e1759..3269fcbe459d2 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/monitoring_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/monitoring_watch.js @@ -5,7 +5,7 @@ */ import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../../common/constants'; /** * {@code MonitoringWatch} system defined watches created by the Monitoring plugin. diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js similarity index 99% rename from x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js index af995d6594a38..02fa99e7f3e16 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js @@ -6,7 +6,7 @@ import { BaseWatch } from './base_watch'; import uuid from 'uuid'; -import { WATCH_TYPES, SORT_ORDERS, COMPARATORS } from '../../../common/constants'; +import { WATCH_TYPES, SORT_ORDERS, COMPARATORS } from '../../../../../common/constants'; import { getTimeUnitLabel } from '../../lib/get_time_unit_label'; import { i18n } from '@kbn/i18n'; import { aggTypes } from './agg_types'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch/watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js similarity index 93% rename from x-pack/legacy/plugins/watcher/public/models/watch/watch.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js index d58a7799c6516..2723fed920675 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch/watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/watch.js @@ -5,7 +5,7 @@ */ import { get, set } from 'lodash'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../../common/constants'; import { JsonWatch } from './json_watch'; import { ThresholdWatch } from './threshold_watch'; import { MonitoringWatch } from './monitoring_watch'; diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_errors/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_errors/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_errors/watch_errors.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/watch_errors.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_errors/watch_errors.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_errors/watch_errors.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_history_item/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js similarity index 91% rename from x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js index a5918cec2764b..785f9d19b23dd 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch_history_item/watch_history_item.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_history_item/watch_history_item.js @@ -6,7 +6,7 @@ import 'moment-duration-format'; import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; import { WatchStatus } from '../watch_status'; export class WatchHistoryItem { diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_status/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/public/models/watch_status/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/index.js diff --git a/x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js similarity index 94% rename from x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js rename to x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js index f213032a93c27..77007ea190386 100644 --- a/x-pack/legacy/plugins/watcher/public/models/watch_status/watch_status.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch_status/watch_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../../common/lib/get_moment'; import { ActionStatus } from '../action_status'; export class WatchStatus { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx similarity index 92% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx index 9c4b16e301b38..010e430c0719a 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit.tsx @@ -16,10 +16,10 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { getActionType } from '../../../../../common/lib/get_action_type'; -import { BaseWatch, ExecutedWatchDetails } from '../../../../../common/types/watch_types'; -import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { getActionType } from '../../../../../../../common/lib/get_action_type'; +import { BaseWatch, ExecutedWatchDetails } from '../../../../../../../common/types/watch_types'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../../../common/constants'; import { JsonWatchEditForm } from './json_watch_edit_form'; import { JsonWatchEditSimulate } from './json_watch_edit_simulate'; import { WatchContext } from '../../watch_context'; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx index 02a54fc9b9279..376aeb205b855 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_form.tsx @@ -20,15 +20,20 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { serializeJsonWatch } from '../../../../../common/lib/serialization'; -import { ErrableFormRow, SectionError } from '../../../../components'; -import { putWatchApiUrl } from '../../../../lib/documentation_links'; +import { serializeJsonWatch } from '../../../../../../../common/lib/serialization'; +import { ErrableFormRow, SectionError, Error as ServerError } from '../../../../components'; import { onWatchSave } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { goToWatchList } from '../../../../lib/navigation'; import { RequestFlyout } from '../request_flyout'; +import { useAppContext } from '../../../../app_context'; export const JsonWatchEditForm = () => { + const { + links: { putWatchApiUrl }, + toasts, + } = useAppContext(); + const { watch, setWatchProperty } = useContext(WatchContext); const { errors } = watch.validate(); @@ -37,9 +42,7 @@ export const JsonWatchEditForm = () => { const [validationError, setValidationError] = useState(null); const [isRequestVisible, setIsRequestVisible] = useState(false); - const [serverError, setServerError] = useState<{ - data: { nessage: string; error: string }; - } | null>(null); + const [serverError, setServerError] = useState(null); const [isSaving, setIsSaving] = useState(false); @@ -192,7 +195,7 @@ export const JsonWatchEditForm = () => { isDisabled={hasErrors} onClick={async () => { setIsSaving(true); - const savedWatch = await onWatchSave(watch); + const savedWatch = await onWatchSave(watch, toasts); if (savedWatch && savedWatch.error) { const { data } = savedWatch.error; setIsSaving(false); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx index e57a875aa4356..7c5de3d8e9298 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx @@ -24,19 +24,19 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { ACTION_MODES, TIME_UNITS } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { ACTION_MODES, TIME_UNITS } from '../../../../../../../common/constants'; import { ExecutedWatchDetails, ExecutedWatchResults, -} from '../../../../../common/types/watch_types'; +} from '../../../../../../../common/types/watch_types'; import { ErrableFormRow } from '../../../../components/form_errors'; import { executeWatch } from '../../../../lib/api'; -import { executeWatchApiUrl } from '../../../../lib/documentation_links'; import { WatchContext } from '../../watch_context'; import { JsonWatchEditSimulateResults } from './json_watch_edit_simulate_results'; import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; +import { useAppContext } from '../../../../app_context'; const actionModeOptions = Object.keys(ACTION_MODES).map(mode => ({ text: ACTION_MODES[mode], @@ -70,6 +70,9 @@ export const JsonWatchEditSimulate = ({ type: string; }>; }) => { + const { + links: { executeWatchApiUrl }, + } = useAppContext(); const { watch } = useContext(WatchContext); // hooks diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx similarity index 99% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx index 1b2b4ab935e8c..4b630f5bc81b4 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate_results.tsx @@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ExecutedWatchDetails, ExecutedWatchResults, -} from '../../../../../common/types/watch_types'; +} from '../../../../../../../common/types/watch_types'; import { getTypeFromAction } from '../../watch_edit_actions'; import { WatchContext } from '../../watch_context'; import { WatchStatus, SectionError } from '../../../../components'; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/request_flyout.tsx similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/request_flyout.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/request_flyout.tsx diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx index aebe8baaee417..3e70e49f42350 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/email_action_fields.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiComboBox, EuiFieldText, EuiFormRow, EuiTextArea } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { EmailAction } from '../../../../../../common/types/action_types'; +import { EmailAction } from '../../../../../../../../common/types/action_types'; interface Props { action: EmailAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx index 1cafb08ca4060..b7ab76d9890bc 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/index_action_fields.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { IndexAction } from '../../../../../../common/types/action_types'; +import { IndexAction } from '../../../../../../../../common/types/action_types'; interface Props { action: IndexAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx index b8bdeaff90821..c09b3c44fde65 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/jira_action_fields.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { JiraAction } from '../../../../../../common/types/action_types'; +import { JiraAction } from '../../../../../../../../common/types/action_types'; interface Props { action: JiraAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx index b70e504519ae5..7da2a22ecd6c4 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/logging_action_fields.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { LoggingAction } from '../../../../../../common/types/action_types'; +import { LoggingAction } from '../../../../../../../../common/types/action_types'; interface Props { action: LoggingAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx index b2b670bf6b91f..3287bdefa08aa 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/pagerduty_action_fields.tsx @@ -7,7 +7,7 @@ import React, { Fragment } from 'react'; import { EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { PagerDutyAction } from '../../../../../../common/types/action_types'; +import { PagerDutyAction } from '../../../../../../../../common/types/action_types'; interface Props { action: PagerDutyAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx index 7b5a598c97eb7..a72cf232d8d09 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/slack_action_fields.tsx @@ -6,7 +6,7 @@ import React, { Fragment } from 'react'; import { EuiComboBox, EuiTextArea, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SlackAction } from '../../../../../../common/types/action_types'; +import { SlackAction } from '../../../../../../../../common/types/action_types'; interface Props { action: SlackAction; diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx similarity index 98% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx index c3784e1ca5516..bdc6f0bcbb717 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ErrableFormRow } from '../../../../../components/form_errors'; -import { WebhookAction } from '../../../../../../common/types/action_types'; +import { WebhookAction } from '../../../../../../../../common/types/action_types'; interface Props { action: WebhookAction; @@ -39,7 +39,7 @@ export const WebhookActionFields: React.FunctionComponent = ({ useEffect(() => { editAction({ key: 'contentType', value: 'application/json' }); // set content-type for threshold watch to json by default - }, []); + }, [editAction]); return ( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/index.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/index.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx similarity index 91% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx index 8b72eb7f19456..4fca772a18217 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_accordion.tsx @@ -21,13 +21,12 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ExecuteDetails } from 'plugins/watcher/models/execute_details/execute_details'; -import { Action } from 'plugins/watcher/models/action'; -import { toastNotifications } from 'ui/notify'; -import { WatchHistoryItem } from 'plugins/watcher/models/watch_history_item'; -import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; -import { ActionType } from '../../../../../common/types/action_types'; -import { ACTION_TYPES, ACTION_MODES } from '../../../../../common/constants'; +import { ExecuteDetails } from 'plugins/watcher/np_ready/application/models/execute_details/execute_details'; +import { Action } from 'plugins/watcher/np_ready/application/models/action'; +import { WatchHistoryItem } from 'plugins/watcher/np_ready/application/models/watch_history_item'; +import { ThresholdWatch } from 'plugins/watcher/np_ready/application/models/watch/threshold_watch'; +import { ActionType } from '../../../../../../../common/types/action_types'; +import { ACTION_TYPES, ACTION_MODES } from '../../../../../../../common/constants'; import { WatchContext } from '../../watch_context'; import { WebhookActionFields, @@ -39,8 +38,8 @@ import { JiraActionFields, } from './action_fields'; import { executeWatch } from '../../../../lib/api'; -import { watchActionsConfigurationMap } from '../../../../lib/documentation_links'; import { SectionError } from '../../../../components'; +import { useAppContext } from '../../../../app_context'; const actionFieldsComponentMap = { [ACTION_TYPES.LOGGING]: LoggingActionFields, @@ -71,6 +70,10 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ settings, actionErrors, }) => { + const { + links: { watchActionsConfigurationMap }, + toasts, + } = useAppContext(); const { watch, setWatchProperty } = useContext(WatchContext); const { actions } = watch; @@ -238,9 +241,9 @@ export const WatchActionsAccordion: React.FunctionComponent = ({ if (actionStatus && actionStatus.lastExecutionSuccessful === false) { const message = actionStatus.lastExecutionReason || action.simulateFailMessage; - return toastNotifications.addDanger(message); + return toasts.addDanger(message); } - return toastNotifications.addSuccess(action.simulateMessage); + return toasts.addSuccess(action.simulateMessage); }} > {action.simulatePrompt} diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx index 82f3352b4e023..d92cccfa00f14 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_dropdown.tsx @@ -16,9 +16,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext, useState } from 'react'; -import { Action } from 'plugins/watcher/models/action'; +import { Action } from 'plugins/watcher/np_ready/application/models/action'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ACTION_TYPES } from '../../../../../common/constants'; +import { ACTION_TYPES } from '../../../../../../../common/constants'; import { WatchContext } from '../../watch_context'; const disabledMessage = i18n.translate( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx similarity index 93% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx index a2e46652429ea..6072f93e53cf6 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { loadSettings } from '../../../../lib/api'; +import { useLoadSettings } from '../../../../lib/api'; import { WatchActionsDropdown } from './threshold_watch_action_dropdown'; import { WatchActionsAccordion } from './threshold_watch_action_accordion'; import { WatchContext } from '../../watch_context'; @@ -22,7 +22,7 @@ interface Props { export const WatchActionsPanel: React.FunctionComponent = ({ actionErrors }) => { const { watch } = useContext(WatchContext); - const { data: settings, isLoading } = loadSettings(); + const { data: settings, isLoading } = useLoadSettings(); return (
    diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx similarity index 95% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index 910d4f1e0b15c..f1b5d2c9eab7b 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -26,9 +26,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TIME_UNITS } from '../../../../../common/constants'; -import { serializeThresholdWatch } from '../../../../../common/lib/serialization'; -import { ErrableFormRow, SectionError } from '../../../../components'; +import { TIME_UNITS } from '../../../../../../../common/constants'; +import { serializeThresholdWatch } from '../../../../../../../common/lib/serialization'; +import { ErrableFormRow, SectionError, Error as ServerError } from '../../../../components'; import { fetchFields, getMatchingIndices, loadIndexPatterns } from '../../../../lib/api'; import { aggTypes } from '../../../../models/watch/agg_types'; import { groupByTypes } from '../../../../models/watch/group_by_types'; @@ -40,6 +40,7 @@ import { WatchActionsPanel } from './threshold_watch_action_panel'; import { getTimeUnitLabel } from '../../../../lib/get_time_unit_label'; import { goToWatchList } from '../../../../lib/navigation'; import { RequestFlyout } from '../request_flyout'; +import { useAppContext } from '../../../../app_context'; const expressionFieldsWithValidation = [ 'aggField', @@ -104,7 +105,7 @@ const getTimeFieldOptions = (fields: any) => { }; interface IOption { label: string; - options: Array<{ value: string; label: string }>; + options: Array<{ value: string; label: string; key?: string }>; } const getIndexOptions = async (patternString: string, indexPatterns: string[]) => { @@ -129,12 +130,14 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = defaultMessage: 'Based on your indices and index patterns', } ), - options: matchingOptions.map(match => { - return { - label: match, - value: match, - }; - }), + options: matchingOptions + .map(match => { + return { + label: match, + value: match, + }; + }) + .sort((a, b) => String(a.label).localeCompare(b.label)), }); } @@ -144,6 +147,7 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = }), options: [ { + key: 'UNIQUE_CHOOSE_KEY', value: patternString, label: patternString, }, @@ -155,7 +159,8 @@ const getIndexOptions = async (patternString: string, indexPatterns: string[]) = export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { // hooks - const [indexPatterns, setIndexPatterns] = useState([]); + const { toasts } = useAppContext(); + const [indexPatterns, setIndexPatterns] = useState([]); const [esFields, setEsFields] = useState([]); const [indexOptions, setIndexOptions] = useState([]); const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); @@ -165,34 +170,33 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const [watchThresholdPopoverOpen, setWatchThresholdPopoverOpen] = useState(false); const [watchDurationPopoverOpen, setWatchDurationPopoverOpen] = useState(false); const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); - const [serverError, setServerError] = useState<{ - data: { nessage: string; error: string }; - } | null>(null); + const [serverError, setServerError] = useState(null); const [isSaving, setIsSaving] = useState(false); const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); const [isRequestVisible, setIsRequestVisible] = useState(false); const { watch, setWatchProperty } = useContext(WatchContext); - const getIndexPatterns = async () => { - const indexPatternObjects = await loadIndexPatterns(); - const titles = indexPatternObjects.map((indexPattern: any) => indexPattern.attributes.title); - setIndexPatterns(titles); - }; + useEffect(() => { + const getIndexPatterns = async () => { + const indexPatternObjects = await loadIndexPatterns(); + const titles = indexPatternObjects.map((indexPattern: any) => indexPattern.attributes.title); + setIndexPatterns(titles); + }; - const loadData = async () => { - if (watch.index && watch.index.length > 0) { - const allEsFields = await getFields(watch.index); - const timeFields = getTimeFieldOptions(allEsFields); - setEsFields(allEsFields); - setTimeFieldOptions(timeFields); - setWatchProperty('timeFields', timeFields); - } - getIndexPatterns(); - }; + const loadData = async () => { + if (watch.index && watch.index.length > 0) { + const allEsFields = await getFields(watch.index); + const timeFields = getTimeFieldOptions(allEsFields); + setEsFields(allEsFields); + setTimeFieldOptions(timeFields); + setWatchProperty('timeFields', timeFields); + } + getIndexPatterns(); + }; - useEffect(() => { loadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const { errors } = watch.validate(); @@ -899,7 +903,7 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { isLoading={isSaving} onClick={async () => { setIsSaving(true); - const savedWatch = await onWatchSave(watch); + const savedWatch = await onWatchSave(watch, toasts); if (savedWatch && savedWatch.error) { setIsSaving(false); return setServerError(savedWatch.error); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx similarity index 83% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index 772f3cc024fe8..a3da7d14c8886 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -18,20 +18,20 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { TimeBuckets } from 'ui/time_buckets'; import dateMath from '@elastic/datemath'; -import chrome from 'ui/chrome'; import moment from 'moment-timezone'; +import { IUiSettingsClient } from 'src/core/public'; import { EuiCallOut, EuiLoadingChart, EuiSpacer, EuiEmptyPrompt, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisualizeOptions } from 'plugins/watcher/models/visualize_options'; -import { ThresholdWatch } from 'plugins/watcher/models/watch/threshold_watch'; -import { npStart } from 'ui/new_platform'; -import { getWatchVisualizationData } from '../../../../lib/api'; +import { VisualizeOptions } from 'plugins/watcher/np_ready/application/models/visualize_options'; +import { ThresholdWatch } from 'plugins/watcher/np_ready/application/models/watch/threshold_watch'; + +import { useGetWatchVisualizationData } from '../../../../lib/api'; import { WatchContext } from '../../watch_context'; import { aggTypes } from '../../../../models/watch/agg_types'; import { comparators } from '../../../../models/watch/comparators'; import { SectionError, Error } from '../../../../components'; +import { useAppContext } from '../../../../app_context'; const customTheme = () => { return { @@ -46,8 +46,7 @@ const customTheme = () => { }; }; -const getTimezone = () => { - const config = chrome.getUiSettingsClient(); +const getTimezone = (config: IUiSettingsClient) => { const DATE_FORMAT_CONFIG_KEY = 'dateFormat:tz'; const isCustomTimezone = !config.isDefault(DATE_FORMAT_CONFIG_KEY); if (isCustomTimezone) { @@ -59,8 +58,7 @@ const getTimezone = () => { return detectedTimezone; } // default to UTC if we can't figure out the timezone - const tzOffset = moment().format('Z'); - return tzOffset; + return moment().format('Z'); }; const getDomain = (watch: any) => { @@ -83,16 +81,20 @@ const getThreshold = (watch: any) => { return watch.threshold.slice(0, comparators[watch.thresholdComparator].requiredValues); }; -const getTimeBuckets = (watch: any) => { +const getTimeBuckets = (watch: any, timeBuckets: any) => { const domain = getDomain(watch); - const timeBuckets = new TimeBuckets(); timeBuckets.setBounds(domain); return timeBuckets; }; export const WatchVisualization = () => { + const { + legacy: { TimeBuckets }, + euiUtils, + uiSettings, + } = useAppContext(); const { watch } = useContext(WatchContext); - const chartsTheme = npStart.plugins.eui_utils.useChartsTheme(); + const chartsTheme = euiUtils.useChartsTheme(); const { index, timeField, @@ -117,7 +119,7 @@ export const WatchVisualization = () => { rangeFrom: domain.min, rangeTo: domain.max, interval, - timezone: getTimezone(), + timezone: getTimezone(uiSettings), }); // Fetching visualization data is independent of watch actions @@ -129,30 +131,33 @@ export const WatchVisualization = () => { data: watchVisualizationData, error, sendRequest: reload, - } = getWatchVisualizationData(watchWithoutActions, visualizeOptions); + } = useGetWatchVisualizationData(watchWithoutActions, visualizeOptions); - useEffect(() => { - // Prevent sending a second request on initial render. - if (isInitialRequest) { - return; - } - - reload(); - }, [ - index, - timeField, - triggerIntervalSize, - triggerIntervalUnit, - aggType, - aggField, - termSize, - termField, - thresholdComparator, - timeWindowSize, - timeWindowUnit, - groupBy, - threshold, - ]); + useEffect( + () => { + // Prevent sending a second request on initial render. + if (isInitialRequest) { + return; + } + reload(); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + groupBy, + threshold, + ] + ); if (isInitialRequest && isLoading) { return ( @@ -190,7 +195,7 @@ export const WatchVisualization = () => { if (watchVisualizationData) { const watchVisualizationDataKeys = Object.keys(watchVisualizationData); - const timezone = getTimezone(); + const timezone = getTimezone(uiSettings); const actualThreshold = getThreshold(watch); let maxY = actualThreshold[actualThreshold.length - 1]; @@ -204,7 +209,7 @@ export const WatchVisualization = () => { const dateFormatter = (d: number) => { return moment(d) .tz(timezone) - .format(getTimeBuckets(watch).getScaledDateFormat()); + .format(getTimeBuckets(watch, new TimeBuckets()).getScaledDateFormat()); }; const aggLabel = aggTypes[watch.aggType].text; return ( diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx similarity index 82% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx index 25daf190dc1b1..9f252d3e542e0 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/watch_edit.tsx @@ -9,13 +9,11 @@ import { isEqual } from 'lodash'; import { EuiPageContent } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { Watch } from 'plugins/watcher/models/watch'; +import { Watch } from 'plugins/watcher/np_ready/application/models/watch'; import { FormattedMessage } from '@kbn/i18n/react'; -import { WATCH_TYPES } from '../../../../common/constants'; -import { BaseWatch } from '../../../../common/types/watch_types'; +import { WATCH_TYPES } from '../../../../../../common/constants'; +import { BaseWatch } from '../../../../../../common/types/watch_types'; import { getPageErrorCode, PageError, SectionLoading, SectionError } from '../../../components'; import { loadWatch } from '../../../lib/api'; import { listBreadcrumb, editBreadcrumb, createBreadcrumb } from '../../../lib/breadcrumbs'; @@ -23,6 +21,7 @@ import { JsonWatchEdit } from './json_watch_edit'; import { ThresholdWatchEdit } from './threshold_watch_edit'; import { MonitoringWatchEdit } from './monitoring_watch_edit'; import { WatchContext } from '../watch_context'; +import { useAppContext } from '../../../app_context'; const getTitle = (watch: BaseWatch) => { if (watch.isNew) { @@ -97,6 +96,10 @@ export const WatchEdit = ({ }; }) => { // hooks + const { + legacy: { MANAGEMENT_BREADCRUMB }, + chrome, + } = useAppContext(); const [{ watch, loadError }, dispatch] = useReducer(watchReducer, { watch: null }); const setWatchProperty = (property: string, value: any) => { @@ -107,33 +110,33 @@ export const WatchEdit = ({ dispatch({ command: 'addAction', payload: action }); }; - const getWatch = async () => { - if (id) { - try { - const loadedWatch = await loadWatch(id); - dispatch({ command: 'setWatch', payload: loadedWatch }); - } catch (error) { - dispatch({ command: 'setError', payload: error }); - } - } else if (type) { - const WatchType = Watch.getWatchTypes()[type]; - if (WatchType) { - dispatch({ command: 'setWatch', payload: new WatchType() }); + useEffect(() => { + const getWatch = async () => { + if (id) { + try { + const loadedWatch = await loadWatch(id); + dispatch({ command: 'setWatch', payload: loadedWatch }); + } catch (error) { + dispatch({ command: 'setError', payload: error }); + } + } else if (type) { + const WatchType = Watch.getWatchTypes()[type]; + if (WatchType) { + dispatch({ command: 'setWatch', payload: new WatchType() }); + } } - } - }; + }; - useEffect(() => { getWatch(); - }, []); + }, [id, type]); useEffect(() => { - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ MANAGEMENT_BREADCRUMB, listBreadcrumb, id ? editBreadcrumb : createBreadcrumb, ]); - }, [id]); + }, [id, chrome, MANAGEMENT_BREADCRUMB]); const errorCode = getPageErrorCode(loadError); if (errorCode) { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_context.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_context.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_context.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_context.ts diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts similarity index 86% rename from x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts index 320ba59e0589e..b93c2c510047d 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/watch_edit_actions.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/watch_edit_actions.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ToastsSetup } from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; import { get } from 'lodash'; -import { ACTION_TYPES, WATCH_TYPES } from '../../../common/constants'; -import { BaseWatch } from '../../../common/types/watch_types'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../../../common/constants'; +import { BaseWatch } from '../../../../../common/types/watch_types'; import { createWatch } from '../../lib/api'; import { goToWatchList } from '../../lib/navigation'; @@ -62,10 +62,10 @@ function createActionsForWatch(watchInstance: BaseWatch) { return watchInstance; } -export async function saveWatch(watch: BaseWatch): Promise { +export async function saveWatch(watch: BaseWatch, toasts: ToastsSetup): Promise { try { await createWatch(watch); - toastNotifications.addSuccess( + toasts.addSuccess( i18n.translate('xpack.watcher.sections.watchEdit.json.saveSuccessNotificationText', { defaultMessage: "Saved '{watchDisplayName}'", values: { @@ -75,11 +75,11 @@ export async function saveWatch(watch: BaseWatch): Promise { ); goToWatchList(); } catch (error) { - return error.response ? { error: error.response } : { error }; + return { error: error?.response.data ?? (error.body || error) }; } } -export async function onWatchSave(watch: BaseWatch): Promise { +export async function onWatchSave(watch: BaseWatch, toasts: ToastsSetup): Promise { const watchActions = watch.watch && watch.watch.actions; const watchData = watchActions ? createActionsForWatch(watch) : watch; @@ -109,7 +109,7 @@ export async function onWatchSave(watch: BaseWatch): Promise { }, }; } - return saveWatch(watchData); + return saveWatch(watchData, toasts); } - return saveWatch(watchData); + return saveWatch(watchData, toasts); } diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx index d5191c56643c2..b2afc0b92509b 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_list/components/watch_list.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_list/components/watch_list.tsx @@ -27,10 +27,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Moment } from 'moment'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../common/constants'; +import { REFRESH_INTERVALS, PAGINATION, WATCH_TYPES } from '../../../../../../common/constants'; import { listBreadcrumb } from '../../../lib/breadcrumbs'; import { getPageErrorCode, @@ -41,12 +39,17 @@ import { SectionLoading, Error, } from '../../../components'; -import { loadWatches } from '../../../lib/api'; -import { watcherGettingStartedUrl } from '../../../lib/documentation_links'; +import { useLoadWatches } from '../../../lib/api'; import { goToCreateThresholdAlert, goToCreateAdvancedWatch } from '../../../lib/navigation'; +import { useAppContext } from '../../../app_context'; export const WatchList = () => { // hooks + const { + chrome, + legacy: { MANAGEMENT_BREADCRUMB }, + links: { watcherGettingStartedUrl }, + } = useAppContext(); const [selection, setSelection] = useState([]); const [watchesToDelete, setWatchesToDelete] = useState([]); // Filter out deleted watches on the client, because the API will return 200 even though some watches @@ -54,10 +57,10 @@ export const WatchList = () => { const [deletedWatches, setDeletedWatches] = useState([]); useEffect(() => { - chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb]); - }, []); + chrome.setBreadcrumbs([MANAGEMENT_BREADCRUMB, listBreadcrumb]); + }, [chrome, MANAGEMENT_BREADCRUMB]); - const { isLoading: isWatchesLoading, data: watches, error } = loadWatches( + const { isLoading: isWatchesLoading, data: watches, error } = useLoadWatches( REFRESH_INTERVALS.WATCH_LIST ); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx similarity index 96% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx index aba4fd0c52a2e..197342bba4180 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_detail.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_detail.tsx @@ -7,7 +7,6 @@ import React, { Fragment, useState, useEffect, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { EuiInMemoryTable, @@ -21,8 +20,9 @@ import { } from '@elastic/eui'; import { ackWatchAction } from '../../../lib/api'; import { WatchStatus } from '../../../components'; -import { PAGINATION } from '../../../../common/constants'; +import { PAGINATION } from '../../../../../../common/constants'; import { WatchDetailsContext } from '../watch_details_context'; +import { useAppContext } from '../../../app_context'; interface ActionError { code: string; @@ -36,6 +36,7 @@ interface ActionStatus { } export const WatchDetail = () => { + const { toasts } = useAppContext(); const { watchDetail } = useContext(WatchDetailsContext); const [actionStatuses, setActionStatuses] = useState([]); @@ -60,7 +61,7 @@ export const WatchDetail = () => { }; }); setActionStatuses(actionStatusesWithErrors); - }, [watchDetail]); + }, [watchDetail, actionErrors, currentActionStatuses]); const baseColumns = [ { @@ -144,7 +145,7 @@ export const WatchDetail = () => { return setActionStatuses(newActionStatusesWithErrors); } catch (e) { setIsActionStatusLoading(false); - toastNotifications.addDanger( + toasts.addDanger( i18n.translate( 'xpack.watcher.sections.watchDetail.watchTable.ackActionErrorMessage', { diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx similarity index 97% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx index bf6ca0c6c43a0..2bc1a0cbace18 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_history.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_history.tsx @@ -23,9 +23,9 @@ import { EuiTitle, } from '@elastic/eui'; -import { PAGINATION } from '../../../../common/constants'; +import { PAGINATION } from '../../../../../../common/constants'; import { WatchStatus, SectionError, Error } from '../../../components'; -import { loadWatchHistory, loadWatchHistoryDetail } from '../../../lib/api'; +import { useLoadWatchHistory, useLoadWatchHistoryDetail } from '../../../lib/api'; import { WatchDetailsContext } from '../watch_details_context'; const watchHistoryTimeSpanOptions = [ @@ -83,12 +83,12 @@ export const WatchHistory = () => { setIsActivated(isActive); } - const { error: historyError, data: history, isLoading } = loadWatchHistory( + const { error: historyError, data: history, isLoading } = useLoadWatchHistory( loadedWatch.id, watchHistoryTimeSpan ); - const { error: watchHistoryDetailsError, data: watchHistoryDetails } = loadWatchHistoryDetail( + const { error: watchHistoryDetailsError, data: watchHistoryDetails } = useLoadWatchHistoryDetail( detailWatchId ); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx similarity index 94% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx index 413e8f638887b..53817c23e72eb 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_status/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/components/watch_status.tsx @@ -17,15 +17,12 @@ import { EuiBadge, EuiButtonEmpty, } from '@elastic/eui'; -import chrome from 'ui/chrome'; -import { MANAGEMENT_BREADCRUMB } from 'ui/management'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { WatchDetail } from './watch_detail'; import { WatchHistory } from './watch_history'; import { listBreadcrumb, statusBreadcrumb } from '../../../lib/breadcrumbs'; -import { loadWatchDetail, deactivateWatch, activateWatch } from '../../../lib/api'; +import { useLoadWatchDetail, deactivateWatch, activateWatch } from '../../../lib/api'; import { WatchDetailsContext } from '../watch_details_context'; import { getPageErrorCode, @@ -34,6 +31,7 @@ import { DeleteWatchesModal, } from '../../../components'; import { goToWatchList } from '../../../lib/navigation'; +import { useAppContext } from '../../../app_context'; interface WatchStatusTab { id: string; @@ -69,11 +67,16 @@ export const WatchStatus = ({ }; }; }) => { + const { + chrome, + legacy: { MANAGEMENT_BREADCRUMB }, + toasts, + } = useAppContext(); const { error: watchDetailError, data: watchDetail, isLoading: isWatchDetailLoading, - } = loadWatchDetail(id); + } = useLoadWatchDetail(id); const [selectedTab, setSelectedTab] = useState(WATCH_EXECUTION_HISTORY_TAB); const [isActivated, setIsActivated] = useState(undefined); @@ -81,8 +84,8 @@ export const WatchStatus = ({ const [isTogglingActivation, setIsTogglingActivation] = useState(false); useEffect(() => { - chrome.breadcrumbs.set([MANAGEMENT_BREADCRUMB, listBreadcrumb, statusBreadcrumb]); - }, [id]); + chrome.setBreadcrumbs([MANAGEMENT_BREADCRUMB, listBreadcrumb, statusBreadcrumb]); + }, [id, chrome, MANAGEMENT_BREADCRUMB]); const errorCode = getPageErrorCode(watchDetailError); @@ -148,7 +151,7 @@ export const WatchStatus = ({ defaultMessage: "Couldn't activate watch", } ); - return toastNotifications.addDanger(message); + return toasts.addDanger(message); } setIsActivated(!isActivated); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_status/watch_details_context.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/watch_details_context.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/public/sections/watch_status/watch_details_context.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_status/watch_details_context.ts diff --git a/x-pack/legacy/plugins/watcher/public/shared_imports.ts b/x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts similarity index 79% rename from x-pack/legacy/plugins/watcher/public/shared_imports.ts rename to x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts index 3d93b882733ab..60445b00c0985 100644 --- a/x-pack/legacy/plugins/watcher/public/shared_imports.ts +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/shared_imports.ts @@ -10,4 +10,4 @@ export { UseRequestConfig, sendRequest, useRequest, -} from '../../../../../src/plugins/es_ui_shared/public/request'; +} from '../../../../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; diff --git a/x-pack/legacy/plugins/watcher/public/index.js b/x-pack/legacy/plugins/watcher/public/np_ready/index.ts similarity index 71% rename from x-pack/legacy/plugins/watcher/public/index.js rename to x-pack/legacy/plugins/watcher/public/np_ready/index.ts index c1b84e76d0008..ff635579316e5 100644 --- a/x-pack/legacy/plugins/watcher/public/index.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/index.ts @@ -3,6 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { WatcherUIPlugin } from './plugin'; -import './register_route'; -import './register_management_sections'; +export const plugin = () => new WatcherUIPlugin(); diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/public/np_ready/plugin.ts new file mode 100644 index 0000000000000..161de9b5fc060 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/np_ready/plugin.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 { Plugin, CoreSetup, CoreStart } from 'src/core/public'; + +import { LegacyDependencies } from './types'; + +interface LegacyPlugins { + __LEGACY: LegacyDependencies; +} + +export class WatcherUIPlugin implements Plugin { + /* TODO: Remove this in future. We need this at mount (setup) but it's only available on start plugins. */ + euiUtils: any = null; + + setup({ application, notifications, http, uiSettings }: CoreSetup, { __LEGACY }: LegacyPlugins) { + application.register({ + id: 'watcher', + title: 'Watcher', + mount: async ( + { + core: { + docLinks, + chrome, + // Waiting for types to be updated. + // @ts-ignore + savedObjects, + i18n: { Context: I18nContext }, + }, + }, + { element } + ) => { + const euiUtils = this.euiUtils!; + const { boot } = await import('./application/boot'); + return boot({ + element, + toasts: notifications.toasts, + http, + uiSettings, + docLinks, + chrome, + euiUtils, + savedObjects: savedObjects.client, + I18nContext, + legacy: { + ...__LEGACY, + }, + }); + }, + }); + } + + start(core: CoreStart, { eui_utils }: any) { + // eslint-disable-next-line @typescript-eslint/camelcase + this.euiUtils = eui_utils; + } + + stop() {} +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js b/x-pack/legacy/plugins/watcher/public/np_ready/types.ts similarity index 63% rename from x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js rename to x-pack/legacy/plugins/watcher/public/np_ready/types.ts index 64b9a14f9c438..22109f99c2c48 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_fields_routes.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/types.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerListRoute } from './register_list_route'; - -export function registerFieldsRoutes(server) { - registerListRoute(server); +export interface LegacyDependencies { + MANAGEMENT_BREADCRUMB: { text: string; href?: string }; + TimeBuckets: any; + licenseStatus: any; } diff --git a/x-pack/legacy/plugins/watcher/public/register_feature.js b/x-pack/legacy/plugins/watcher/public/register_feature.js deleted file mode 100644 index 5dd4f28f03bc5..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_feature.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'watcher', - title: 'Watcher', // This is a product name so we don't translate it. - description: i18n.translate('xpack.watcher.watcherDescription', { - defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.' - }), - icon: 'watchesApp', - path: '/app/kibana#/management/elasticsearch/watcher/watches', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN - }; -}); diff --git a/x-pack/legacy/plugins/watcher/public/register_feature.ts b/x-pack/legacy/plugins/watcher/public/register_feature.ts new file mode 100644 index 0000000000000..0de41e09f788e --- /dev/null +++ b/x-pack/legacy/plugins/watcher/public/register_feature.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; + +npSetup.plugins.home.featureCatalogue.register({ + id: 'watcher', + title: 'Watcher', // This is a product name so we don't translate it. + category: FeatureCatalogueCategory.ADMIN, + description: i18n.translate('xpack.watcher.watcherDescription', { + defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', + }), + icon: 'watchesApp', + path: '/app/kibana#/management/elasticsearch/watcher/watches', + showOnHomePage: true, +}); diff --git a/x-pack/legacy/plugins/watcher/public/register_management_sections.js b/x-pack/legacy/plugins/watcher/public/register_management_sections.js deleted file mode 100644 index 886ac7d28db64..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_management_sections.js +++ /dev/null @@ -1,60 +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 { management } from 'ui/management'; -import { i18n } from '@kbn/i18n'; - -management.getSection('elasticsearch').register('watcher', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { - defaultMessage: 'Watcher', - }), - order: 6, - url: '#/management/elasticsearch/watcher/', -}); - -management.getSection('elasticsearch/watcher').register('watches', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.watchesDisplayName', { - defaultMessage: 'Watches', - }), - order: 1, -}); - -management.getSection('elasticsearch/watcher').register('watch', { - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('status', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.statusDisplayName', { - defaultMessage: 'Status', - }), - order: 1, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('edit', { - display: i18n.translate('xpack.watcher.sections.watchList.managementSection.editDisplayName', { - defaultMessage: 'Edit', - }), - order: 2, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('new', { - display: i18n.translate( - 'xpack.watcher.sections.watchList.managementSection.newWatchDisplayName', - { - defaultMessage: 'New Watch', - } - ), - order: 1, - visible: false, -}); - -management.getSection('elasticsearch/watcher/watch').register('history-item', { - order: 1, - visible: false, -}); diff --git a/x-pack/legacy/plugins/watcher/public/register_route.js b/x-pack/legacy/plugins/watcher/public/register_route.js deleted file mode 100644 index c58be17bc6e75..0000000000000 --- a/x-pack/legacy/plugins/watcher/public/register_route.js +++ /dev/null @@ -1,68 +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 { SavedObjectsClientProvider } from 'ui/saved_objects'; -import routes from 'ui/routes'; -import { management } from 'ui/management'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import template from './app.html'; -import { App } from './app'; -import { setHttpClient, setSavedObjectsClient } from './lib/api'; -import { I18nContext } from 'ui/i18n'; -import { manageAngularLifecycle } from './lib/manage_angular_lifecycle'; -import { PLUGIN } from '../common/constants'; -import { LICENSE_STATUS_UNAVAILABLE, LICENSE_STATUS_INVALID } from '../../../common/constants'; - -let elem; -const renderReact = async (elem, licenseStatus) => { - render( - - - , - elem - ); -}; -routes.when('/management/elasticsearch/watcher/:param1?/:param2?/:param3?/:param4?', { - template, - controller: class WatcherController { - constructor($injector, $scope, $http, Private) { - const $route = $injector.get('$route'); - const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); - - // clean up previously rendered React app if one exists - // this happens because of React Router redirects - elem && unmountComponentAtNode(elem); - setSavedObjectsClient(Private(SavedObjectsClientProvider)); - // NOTE: We depend upon Angular's $http service because it's decorated with interceptors, - // e.g. to check license status per request. - setHttpClient($http); - $scope.$$postDigest(() => { - elem = document.getElementById('watchReactRoot'); - renderReact(elem, licenseStatus); - manageAngularLifecycle($scope, $route, elem); - }); - } - }, - controllerAs: 'watchRoute', -}); - -routes.defaults(/\/management/, { - resolve: { - watcherManagementSection: () => { - const watchesSection = management.getSection('elasticsearch/watcher'); - const licenseStatus = xpackInfo.get(`features.${PLUGIN.ID}`); - const { status } = licenseStatus; - - if (status === LICENSE_STATUS_INVALID || status === LICENSE_STATUS_UNAVAILABLE) { - return watchesSection.hide(); - } - - watchesSection.show(); - - }, - }, -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js deleted file mode 100644 index b0ca090601062..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/call_with_internal_user_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const _callWithInternalUser = once((server) => { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - return callWithInternalUser; -}); - -export const callWithInternalUserFactory = (server) => { - return (...args) => { - return _callWithInternalUser(server)(...args); - }; -}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index f60f825b98004..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../elasticsearch_js_plugin'; - -const callWithRequest = once((server) => { - const config = { plugins: [ elasticsearchJsPlugin ] }; - const cluster = server.plugins.elasticsearch.createCluster('watcher', config); - - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js deleted file mode 100644 index 787814d87dff9..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_request_factory/index.js +++ /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 { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js b/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js deleted file mode 100644 index 87b5ff5426c9d..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/index.js +++ /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 { elasticsearchJsPlugin } from './elasticsearch_js_plugin'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index f9c102be7a1ff..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index 467cc4fcdae1f..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return the correct Boom object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 85e0b2b3033ad..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js deleted file mode 100644 index f275f15637091..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/index.js +++ /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. - */ - -export { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 3295113d38ee5..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100644 index 2df2e4b802e1a..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps an error thrown by the ES JS client into a Boom error response and returns it - * - * @param err Object Error thrown by ES JS client - * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages - * @return Object Boom error response - */ -export function wrapEsError(err, statusCodeToMessageMap = {}) { - - const statusCode = err.statusCode; - - // If no custom message if specified for the error's status code, just - // wrap the error as a Boom error response and return it - if (!statusCodeToMessageMap[statusCode]) { - return Boom.boomify(err, { statusCode }); - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return new Boom(message, { statusCode }); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index ffd915c513362..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/error_wrappers/wrap_unknown_error.js +++ /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 Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index 76fdf7b36c3d0..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from 'lodash'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js b/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js deleted file mode 100644 index 441648a8701e0..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/index.js +++ /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 { isEsErrorFactory } from './is_es_error_factory'; diff --git a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js deleted file mode 100644 index 80daac5bd496d..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/is_es_error_factory/is_es_error_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { memoize } from 'lodash'; - -const esErrorsFactory = memoize((server) => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server) { - const esErrors = esErrorsFactory(server); - return function isEsError(err) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 5b34108c9c1c0..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; -import { LICENSE_STATUS_VALID } from '../../../../../common/constants/license_status'; - -export const licensePreRoutingFactory = once((server) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - const { status } = licenseCheckResults; - - if (status !== LICENSE_STATUS_VALID) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - throw wrapCustomError(error, statusCode); - } - - return null; - } - - return licensePreRouting; -}); - diff --git a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/index.ts similarity index 55% rename from x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/index.ts index a56a50e2864a5..3f5e1a91209ea 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/call_with_internal_user_factory/index.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/index.ts @@ -3,5 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializerContext } from 'src/core/server'; +import { WatcherServerPlugin } from './plugin'; -export { callWithInternalUserFactory } from './call_with_internal_user_factory'; +export const plugin = (ctx: PluginInitializerContext) => new WatcherServerPlugin(); diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts new file mode 100644 index 0000000000000..eaec9cd91b23c --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/call_with_request_factory.ts @@ -0,0 +1,28 @@ +/* + * 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 { ElasticsearchServiceSetup } from 'src/core/server'; +import { once } from 'lodash'; +import { elasticsearchJsPlugin } from './elasticsearch_js_plugin'; + +const callWithRequest = once((elasticsearchService: ElasticsearchServiceSetup) => { + const config = { plugins: [elasticsearchJsPlugin] }; + return elasticsearchService.createClient('watcher', config); +}); + +export const callWithRequestFactory = ( + elasticsearchService: ElasticsearchServiceSetup, + request: any +) => { + return (...args: any[]) => { + return ( + callWithRequest(elasticsearchService) + .asScoped(request) + // @ts-ignore + .callAsCurrentUser(...args) + ); + }; +}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts similarity index 84% rename from x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts index ad42388beea1e..240e93e160fe0 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/elasticsearch_js_plugin/elasticsearch_js_plugin.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/elasticsearch_js_plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const elasticsearchJsPlugin = (Client, config, components) => { +export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { const ca = components.clientAction.factory; Client.prototype.watcher = components.clientAction.namespaceFactory(); @@ -21,19 +21,19 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_deactivate', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'PUT' + method: 'PUT', }); /** @@ -47,19 +47,19 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_activate', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'PUT' + method: 'PUT', }); /** @@ -74,23 +74,23 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>/_ack/<%=action%>', req: { id: { type: 'string', - required: true + required: true, }, action: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'POST' + method: 'POST', }); /** @@ -105,22 +105,22 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' + type: 'duration', }, force: { - type: 'boolean' - } + type: 'boolean', + }, }, url: { fmt: '/_watcher/watch/<%=id%>', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, - method: 'DELETE' + method: 'DELETE', }); /** @@ -132,14 +132,14 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { - fmt: '/_watcher/watch/_execute' + fmt: '/_watcher/watch/_execute', }, needBody: true, - method: 'POST' + method: 'POST', }); /** @@ -155,10 +155,10 @@ export const elasticsearchJsPlugin = (Client, config, components) => { req: { id: { type: 'string', - required: true - } - } - } + required: true, + }, + }, + }, }); /** @@ -172,20 +172,20 @@ export const elasticsearchJsPlugin = (Client, config, components) => { params: { masterTimeout: { name: 'master_timeout', - type: 'duration' - } + type: 'duration', + }, }, url: { fmt: '/_watcher/watch/<%=id%>', req: { id: { type: 'string', - required: true - } - } + required: true, + }, + }, }, needBody: true, - method: 'PUT' + method: 'PUT', }); /** @@ -196,9 +196,9 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.restart = ca({ params: {}, url: { - fmt: '/_watcher/_restart' + fmt: '/_watcher/_restart', }, - method: 'PUT' + method: 'PUT', }); /** @@ -209,9 +209,9 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.start = ca({ params: {}, url: { - fmt: '/_watcher/_start' + fmt: '/_watcher/_start', }, - method: 'PUT' + method: 'PUT', }); /** @@ -222,8 +222,8 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.stats = ca({ params: {}, url: { - fmt: '/_watcher/stats' - } + fmt: '/_watcher/stats', + }, }); /** @@ -234,8 +234,8 @@ export const elasticsearchJsPlugin = (Client, config, components) => { watcher.stop = ca({ params: {}, url: { - fmt: '/_watcher/_stop' + fmt: '/_watcher/_stop', }, - method: 'PUT' + method: 'PUT', }); }; diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts similarity index 64% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts index eb76d5d3731cf..d762b05f01d79 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts @@ -5,9 +5,9 @@ */ import { get } from 'lodash'; -import { ES_SCROLL_SETTINGS } from '../../../common/constants'; +import { ES_SCROLL_SETTINGS } from '../../../../common/constants'; -export function fetchAllFromScroll(response, callWithRequest, hits = []) { +export function fetchAllFromScroll(response: any, callWithRequest: any, hits: any[] = []) { const newHits = get(response, 'hits.hits', []); const scrollId = get(response, '_scroll_id'); @@ -17,12 +17,11 @@ export function fetchAllFromScroll(response, callWithRequest, hits = []) { return callWithRequest('scroll', { body: { scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - scroll_id: scrollId - } - }) - .then(innerResponse => { - return fetchAllFromScroll(innerResponse, callWithRequest, hits); - }); + scroll_id: scrollId, + }, + }).then((innerResponse: any) => { + return fetchAllFromScroll(innerResponse, callWithRequest, hits); + }); } return Promise.resolve(hits); diff --git a/x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/fetch_all_from_scroll/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/fetch_all_from_scroll/index.ts diff --git a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts similarity index 84% rename from x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts index 81e0c494e28b3..a9a3c61472d8c 100644 --- a/x-pack/legacy/plugins/watcher/public/lib/documentation_links/index.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './documentation_links'; +export { isEsError } from './is_es_error'; diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts similarity index 55% rename from x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts index bef26fbb9b267..4137293cf39c0 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_history_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/is_es_error/is_es_error.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerLoadRoute } from './register_load_route'; +import * as legacyElasticsearch from 'elasticsearch'; -export function registerHistoryRoutes(server) { - registerLoadRoute(server); +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; } diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js similarity index 71% rename from x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js index ed4a51a11b7cd..fc01e42e6fdf2 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; +import { kibanaResponseFactory } from '../../../../../../../../../src/core/server'; import { licensePreRoutingFactory } from '../license_pre_routing_factory'; -import { LICENSE_STATUS_VALID, LICENSE_STATUS_EXPIRED } from '../../../../../../common/constants/license_status'; +import { LICENSE_STATUS_VALID, LICENSE_STATUS_EXPIRED } from '../../../../../../../common/constants/license_status'; describe('license_pre_routing_factory', () => { describe('#reportingFeaturePreRoutingFactory', () => { @@ -27,13 +28,6 @@ describe('license_pre_routing_factory', () => { }; }); - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - describe('status is not valid', () => { beforeEach(() => { mockLicenseCheckResults = { @@ -42,13 +36,10 @@ describe('license_pre_routing_factory', () => { }); it ('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); + const licensePreRouting = licensePreRoutingFactory(mockServer, () => {}); const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException((response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); + const response = licensePreRouting({}, stubRequest, kibanaResponseFactory); + expect(response.status).to.be(403); }); }); @@ -60,9 +51,9 @@ describe('license_pre_routing_factory', () => { }); it ('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); + const licensePreRouting = licensePreRoutingFactory(mockServer, () => null); const stubRequest = {}; - const response = licensePreRouting(stubRequest); + const response = licensePreRouting({}, stubRequest, kibanaResponseFactory); expect(response).to.be(null); }); }); diff --git a/x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/license_pre_routing_factory/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts new file mode 100644 index 0000000000000..d2f4967246104 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; +import { PLUGIN } from '../../../../common/constants'; +import { LICENSE_STATUS_VALID } from '../../../../../../common/constants/license_status'; +import { ServerShim } from '../../types'; + +export const licensePreRoutingFactory = ( + server: ServerShim, + handler: RequestHandler +): RequestHandler => { + const xpackMainPlugin = server.plugins.xpack_main; + + // License checking and enable/disable logic + return function licensePreRouting( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); + const { status } = licenseCheckResults; + + if (status !== LICENSE_STATUS_VALID) { + return response.customError({ + body: { + message: licenseCheckResults.messsage, + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; +}; diff --git a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js rename to x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts index 65f2867662bdd..39e82e7db8964 100644 --- a/x-pack/legacy/plugins/watcher/server/lib/normalized_field_types/normalized_field_types.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/lib/normalized_field_types/normalized_field_types.ts @@ -5,12 +5,12 @@ */ export const normalizedFieldTypes = { - 'long': 'number', - 'integer': 'number', - 'short': 'number', - 'byte': 'number', - 'double': 'number', - 'float': 'number', - 'half_float': 'number', - 'scaled_float': 'number' + long: 'number', + integer: 'number', + short: 'number', + byte: 'number', + double: 'number', + float: 'number', + half_float: 'number', + scaled_float: 'number', }; diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js index 456768c8c02ec..430669ab26c50 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action_status/__tests__/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { ActionStatus } from '../action_status'; -import { ACTION_STATES } from '../../../../common/constants'; +import { ACTION_STATES } from '../../../../../common/constants'; import moment from 'moment'; describe('action_status', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js index eeedf9aefe5f6..7f724cf68211f 100644 --- a/x-pack/legacy/plugins/watcher/server/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js @@ -6,8 +6,8 @@ import { get } from 'lodash'; import { badImplementation, badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; -import { ACTION_STATES } from '../../../common/constants'; +import { getMoment } from '../../../../common/lib/get_moment'; +import { ACTION_STATES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; export class ActionStatus { diff --git a/x-pack/legacy/plugins/watcher/server/models/action_status/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/action_status/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/__tests__/execute_details.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/__tests__/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/__tests__/execute_details.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/__tests__/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/execute_details.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/execute_details.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/execute_details.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/execute_details.js diff --git a/x-pack/legacy/plugins/watcher/server/models/execute_details/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/execute_details/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/execute_details/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/__tests__/fields.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/__tests__/fields.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/__tests__/fields.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/__tests__/fields.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/fields.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/fields.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/fields.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/fields.js diff --git a/x-pack/legacy/plugins/watcher/server/models/fields/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/fields/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/fields/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/fields/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/__tests__/settings.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/__tests__/settings.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/settings/__tests__/settings.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/__tests__/settings.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/settings/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/settings/settings.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/settings/settings.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js index 95a1db7533f41..55622117efedf 100644 --- a/x-pack/legacy/plugins/watcher/server/models/settings/settings.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/settings/settings.js @@ -5,7 +5,7 @@ */ import { merge } from 'lodash'; -import { ACTION_TYPES } from '../../../common/constants'; +import { ACTION_TYPES } from '../../../../common/constants'; function isEnabledByDefault(actionType) { switch (actionType) { diff --git a/x-pack/legacy/plugins/watcher/server/models/visualize_options/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/visualize_options/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/visualize_options/visualize_options.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/visualize_options.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/visualize_options/visualize_options.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/visualize_options/visualize_options.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js index f96274594872a..6a6df7d6f7f74 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js @@ -6,7 +6,7 @@ import { get, map, pick } from 'lodash'; import { badRequest } from 'boom'; -import { Action } from '../../../common/models/action'; +import { Action } from '../../../../common/models/action'; import { WatchStatus } from '../watch_status'; import { i18n } from '@kbn/i18n'; import { WatchErrors } from '../watch_errors'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/base_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js similarity index 93% rename from x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js index e319cc1bc277b..0b011ca33a76b 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js @@ -6,8 +6,8 @@ import { isEmpty, cloneDeep, has, merge } from 'lodash'; import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; -import { serializeJsonWatch } from '../../../common/lib/serialization'; +import { WATCH_TYPES } from '../../../../common/constants'; +import { serializeJsonWatch } from '../../../../common/lib/serialization'; export class JsonWatch extends BaseWatch { // This constructor should not be used directly. diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/json_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js similarity index 88% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js index 2bdd03e23c6dc..72c725eda2bd1 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/get_watch_type.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/get_watch_type.js @@ -5,7 +5,7 @@ */ import { get, contains, values } from 'lodash'; -import { WATCH_TYPES } from '../../../../../common/constants'; +import { WATCH_TYPES } from '../../../../../../common/constants'; export function getWatchType(watchJson) { const type = get(watchJson, 'metadata.xpack.type'); diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/lib/get_watch_type/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/lib/get_watch_type/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js index 977c62726a038..7f29d41b20fb3 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.js @@ -7,7 +7,7 @@ import { merge } from 'lodash'; import { badRequest } from 'boom'; import { BaseWatch } from './base_watch'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; export class MonitoringWatch extends BaseWatch { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/monitoring_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/monitoring_watch.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js index 04239ab6e1b5f..a7524bcc7c4db 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/__tests__/format_visualize_data.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/__tests__/format_visualize_data.js @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { AGG_TYPES } from '../../../../../common/constants'; +import { AGG_TYPES } from '../../../../../../common/constants'; import { formatVisualizeData } from '../format_visualize_data'; describe('watch', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js similarity index 95% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js index ab9daf6f636a1..c3b73d23d96b1 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/build_visualize_query.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/build_visualize_query.js @@ -5,8 +5,8 @@ */ import { cloneDeep } from 'lodash'; -import { buildInput } from '../../../../common/lib/serialization'; -import { AGG_TYPES } from '../../../../common/constants'; +import { buildInput } from '../../../../../common/lib/serialization'; +import { AGG_TYPES } from '../../../../../common/constants'; /* input.search.request.body.query.bool.filter.range diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/count_terms.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/count_terms.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.date.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.json b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.json similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/data_samples/non_count_terms.query.json rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/data_samples/non_count_terms.query.json diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js index 90cdc9464e8c5..19d41d2491cf5 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/format_visualize_data.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/format_visualize_data.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AGG_TYPES } from '../../../../common/constants'; +import { AGG_TYPES } from '../../../../../common/constants'; export function formatVisualizeData({ aggType, termField }, results) { if (aggType === AGG_TYPES.COUNT && !Boolean(termField)) { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js index cb40c46ac6435..db662902d0f4d 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js @@ -6,8 +6,8 @@ import { merge } from 'lodash'; import { BaseWatch } from '../base_watch'; -import { WATCH_TYPES, COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; -import { serializeThresholdWatch } from '../../../../common/lib/serialization'; +import { WATCH_TYPES, COMPARATORS, SORT_ORDERS } from '../../../../../common/constants'; +import { serializeThresholdWatch } from '../../../../../common/lib/serialization'; import { buildVisualizeQuery } from './build_visualize_query'; import { formatVisualizeData } from './format_visualize_data'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js index 4a0b7b657bbc6..6226a702d7f3c 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/threshold_watch/threshold_watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { COMPARATORS, SORT_ORDERS } from '../../../../common/constants'; +import { COMPARATORS, SORT_ORDERS } from '../../../../../common/constants'; import { WatchErrors } from '../../watch_errors'; import { ThresholdWatch } from './threshold_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch/watch.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js index c75afc62c4c4b..10b021dcbedf6 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.js @@ -6,7 +6,7 @@ import { set } from 'lodash'; import { badRequest } from 'boom'; -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { JsonWatch } from './json_watch'; import { MonitoringWatch } from './monitoring_watch'; import { ThresholdWatch } from './threshold_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js index 2895c23083def..c419c28561730 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch/watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/watch.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { WATCH_TYPES } from '../../../common/constants'; +import { WATCH_TYPES } from '../../../../common/constants'; import { Watch } from './watch'; import { JsonWatch } from './json_watch'; import { MonitoringWatch } from './monitoring_watch'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.test.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_errors/watch_errors.test.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_errors/watch_errors.test.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/__tests__/watch_history_item.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/__tests__/watch_history_item.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/__tests__/watch_history_item.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/__tests__/watch_history_item.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js similarity index 97% rename from x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js index 617f758571742..5172e590fc63e 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_history_item/watch_history_item.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_history_item/watch_history_item.js @@ -5,7 +5,7 @@ */ import { badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../common/lib/get_moment'; import { get, cloneDeep } from 'lodash'; import { WatchStatus } from '../watch_status'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js similarity index 99% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js index e29c8dd2a529e..9a045fa4b5a7f 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_status/__tests__/watch_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/__tests__/watch_status.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { WatchStatus } from '../watch_status'; -import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../common/constants'; +import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../../common/constants'; import moment from 'moment'; describe('watch_status', () => { diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/index.js similarity index 100% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/index.js diff --git a/x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js similarity index 98% rename from x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js rename to x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js index b7cffe16ca0bc..1e3d1d3064cb4 100644 --- a/x-pack/legacy/plugins/watcher/server/models/watch_status/watch_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch_status/watch_status.js @@ -6,9 +6,9 @@ import { get, map, forEach, max } from 'lodash'; import { badRequest } from 'boom'; -import { getMoment } from '../../../common/lib/get_moment'; +import { getMoment } from '../../../../common/lib/get_moment'; import { ActionStatus } from '../action_status'; -import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../common/constants'; +import { ACTION_STATES, WATCH_STATES, WATCH_STATE_COMMENTS } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; function getActionStatusTotals(watchStatus) { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts new file mode 100644 index 0000000000000..2e8c81efa19c0 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts @@ -0,0 +1,51 @@ +/* + * 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 { first } from 'rxjs/operators'; +import { Plugin, CoreSetup } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; +import { PLUGIN } from '../../common/constants'; +import { ServerShim, RouteDependencies } from './types'; + +import { registerLicenseChecker } from '../../../../server/lib/register_license_checker'; +import { registerSettingsRoutes } from './routes/api/settings'; +import { registerIndicesRoutes } from './routes/api/indices'; +import { registerLicenseRoutes } from './routes/api/license'; +import { registerWatchesRoutes } from './routes/api/watches'; +import { registerWatchRoutes } from './routes/api/watch'; +import { registerListFieldsRoute } from './routes/api/register_list_fields_route'; +import { registerLoadHistoryRoute } from './routes/api/register_load_history_route'; + +export class WatcherServerPlugin implements Plugin { + async setup( + { http, elasticsearch: elasticsearchService }: CoreSetup, + { __LEGACY: serverShim }: { __LEGACY: ServerShim } + ) { + const elasticsearch = await elasticsearchService.adminClient$.pipe(first()).toPromise(); + const router = http.createRouter(); + const routeDependencies: RouteDependencies = { + elasticsearch, + elasticsearchService, + router, + }; + // Register license checker + registerLicenseChecker( + serverShim as any, + PLUGIN.ID, + PLUGIN.getI18nName(i18n), + PLUGIN.MINIMUM_LICENSE_REQUIRED + ); + + registerListFieldsRoute(routeDependencies, serverShim); + registerLoadHistoryRoute(routeDependencies, serverShim); + registerIndicesRoutes(routeDependencies, serverShim); + registerLicenseRoutes(routeDependencies, serverShim); + registerSettingsRoutes(routeDependencies, serverShim); + registerWatchesRoutes(routeDependencies, serverShim); + registerWatchRoutes(routeDependencies, serverShim); + } + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/indices/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts new file mode 100644 index 0000000000000..6b6b643dc4adf --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_get_route.ts @@ -0,0 +1,92 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { reduce, size } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function getIndexNamesFromAliasesResponse(json: Record) { + return reduce( + json, + (list, { aliases }, indexName) => { + list.push(indexName); + if (size(aliases) > 0) { + list.push(...Object.keys(aliases)); + } + return list; + }, + [] as string[] + ); +} + +function getIndices(callWithRequest: any, pattern: string, limit = 10) { + return callWithRequest('indices.getAlias', { + index: pattern, + ignore: [404], + }).then((aliasResult: any) => { + if (aliasResult.status !== 404) { + const indicesFromAliasResponse = getIndexNamesFromAliasesResponse(aliasResult); + return indicesFromAliasResponse.slice(0, limit); + } + + const params = { + index: pattern, + ignore: [404], + body: { + size: 0, // no hits + aggs: { + indices: { + terms: { + field: '_index', + size: limit, + }, + }, + }, + }, + }; + + return callWithRequest('search', params).then((response: any) => { + if (response.status === 404 || !response.aggregations) { + return []; + } + return response.aggregations.indices.buckets.map((bucket: any) => bucket.key); + }); + }); +} + +export function registerGetRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { pattern } = request.body; + + try { + const indices = await getIndices(callWithRequest, pattern); + return response.ok({ body: { indices } }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/indices', + validate: { + body: schema.object({}, { allowUnknowns: true }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts index 41b2f8dba7a1f..647a85c311532 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_indices_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/indices/register_indices_routes.ts @@ -5,7 +5,8 @@ */ import { registerGetRoute } from './register_get_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerIndicesRoutes(server) { - registerGetRoute(server); +export function registerIndicesRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerGetRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts index fe890719a0a7d..c5965d9315b01 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_license_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_license_routes.ts @@ -5,7 +5,8 @@ */ import { registerRefreshRoute } from './register_refresh_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerLicenseRoutes(server) { - registerRefreshRoute(server); +export function registerLicenseRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerRefreshRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts similarity index 50% rename from x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts index cbd5dc7f6631f..08f1f26a84a4f 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/license/register_refresh_route.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/license/register_refresh_route.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; +import { RequestHandler } from 'src/core/server'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; /* In order for the client to have the most up-to-date snapshot of the current license, @@ -12,17 +14,16 @@ it needs to make a round-trip to the kibana server. This refresh endpoint is pro for when the client needs to check the license, but doesn't need to pull data from the server for any reason, i.e., when adding a new watch. */ -export function registerRefreshRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); +export function registerRefreshRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = (ctx, request, response) => { + return response.ok({ body: { success: true } }); + }; - server.route({ - path: '/api/watcher/license/refresh', - method: 'GET', - handler: () => { - return { success: true }; + deps.router.get( + { + path: '/api/watcher/license/refresh', + validate: false, }, - config: { - pre: [ licensePreRouting ] - } - }); + licensePreRoutingFactory(legacy, handler) + ); } diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts new file mode 100644 index 0000000000000..f3222d24f0adf --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_list_fields_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Fields } from '../../models/fields'; +import { RouteDependencies, ServerShim } from '../../types'; + +function fetchFields(callWithRequest: any, indexes: string[]) { + const params = { + index: indexes, + fields: ['*'], + ignoreUnavailable: true, + allowNoIndices: true, + ignore: 404, + }; + + return callWithRequest('fieldCaps', params); +} + +export function registerListFieldsRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { indexes } = request.body; + + try { + const fieldsResponse = await fetchFields(callWithRequest, indexes); + const json = fieldsResponse.status === 404 ? { fields: [] } : fieldsResponse; + const fields = Fields.fromUpstreamJson(json); + return response.ok({ body: fields.downstreamJson }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ + statusCode: e.statusCode, + body: { + message: e.message, + }, + }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/fields', + validate: { + body: schema.object({ + indexes: schema.arrayOf(schema.string()), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts new file mode 100644 index 0000000000000..d62e4f48c5629 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/register_load_history_route.ts @@ -0,0 +1,77 @@ +/* + * 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 { get } from 'lodash'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { isEsError } from '../../lib/is_es_error'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../types'; +// @ts-ignore +import { WatchHistoryItem } from '../../models/watch_history_item'; + +function fetchHistoryItem(callWithRequest: any, watchHistoryItemId: string) { + return callWithRequest('search', { + index: INDEX_NAMES.WATCHER_HISTORY, + body: { + query: { + bool: { + must: [{ term: { _id: watchHistoryItemId } }], + }, + }, + }, + }); +} + +export function registerLoadHistoryRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const id = request.params.id; + + try { + const responseFromES = await fetchHistoryItem(callWithRequest, id); + const hit = get(responseFromES, 'hits.hits[0]'); + if (!hit) { + return response.notFound({ body: `Watch History Item with id = ${id} not found` }); + } + const watchHistoryItemJson = get(hit, '_source'); + const watchId = get(hit, '_source.watch_id'); + const json = { + id, + watchId, + watchHistoryItemJson, + includeDetails: true, + }; + + const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); + return response.ok({ + body: { watchHistoryItem: watchHistoryItem.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/history/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/settings/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts new file mode 100644 index 0000000000000..710d079d810da --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_load_route.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IClusterClient, RequestHandler } from 'src/core/server'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Settings } from '../../../models/settings'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchClusterSettings(client: IClusterClient) { + return client.callAsInternalUser('cluster.getSettings', { + includeDefaults: true, + filterPath: '**.xpack.notification', + }); +} + +export function registerLoadRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + try { + const settings = await fetchClusterSettings(deps.elasticsearch); + return response.ok({ body: Settings.fromUpstreamJson(settings).downstreamJson }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + deps.router.get( + { + path: '/api/watcher/settings', + validate: false, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts index eefb320e9b1d9..0b24ec0e90bd4 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_settings_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/settings/register_settings_routes.ts @@ -5,7 +5,8 @@ */ import { registerLoadRoute } from './register_load_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerSettingsRoutes(server) { - registerLoadRoute(server); +export function registerSettingsRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerLoadRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/action/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts new file mode 100644 index 0000000000000..d0cc0a27e87ff --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_acknowledge_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { get } from 'lodash'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../../lib/call_with_request_factory'; +import { isEsError } from '../../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { WatchStatus } from '../../../../models/watch_status'; +import { RouteDependencies, ServerShim } from '../../../../types'; + +function acknowledgeAction(callWithRequest: any, watchId: string, actionId: string) { + return callWithRequest('watcher.ackWatch', { + id: watchId, + action: actionId, + }); +} + +export function registerAcknowledgeRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { watchId, actionId } = request.params; + + try { + const hit = await acknowledgeAction(callWithRequest, watchId, actionId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { watchStatus: watchStatus.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/action/{actionId}/acknowledge', + validate: { + params: schema.object({ + watchId: schema.string(), + actionId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts similarity index 61% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts index 6f2c86664420b..022c844867938 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_action_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/action/register_action_routes.ts @@ -5,7 +5,8 @@ */ import { registerAcknowledgeRoute } from './register_acknowledge_route'; +import { RouteDependencies, ServerShim } from '../../../../types'; -export function registerActionRoutes(server) { - registerAcknowledgeRoute(server); +export function registerActionRoutes(server: RouteDependencies, legacy: ServerShim) { + registerAcknowledgeRoute(server, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts new file mode 100644 index 0000000000000..28c482124aaee --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_activate_route.ts @@ -0,0 +1,66 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchStatus } from '../../../models/watch_status'; + +function activateWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.activateWatch', { + id: watchId, + }); +} + +export function registerActivateRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + const hit = await activateWatch(callWithRequest, watchId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { + watchStatus: watchStatus.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/activate', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts new file mode 100644 index 0000000000000..ac87066379a20 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_deactivate_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchStatus } from '../../../models/watch_status'; + +function deactivateWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.deactivateWatch', { + id: watchId, + }); +} + +export function registerDeactivateRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + const hit = await deactivateWatch(callWithRequest, watchId); + const watchStatusJson = get(hit, 'status'); + const json = { + id: watchId, + watchStatusJson, + }; + + const watchStatus = WatchStatus.fromUpstreamJson(json); + return response.ok({ + body: { + watchStatus: watchStatus.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{watchId}/deactivate', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts new file mode 100644 index 0000000000000..3402cc283dba0 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function deleteWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.deleteWatch', { + id: watchId, + }); +} + +export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const { watchId } = request.params; + + try { + await deleteWatch(callWithRequest, watchId); + return response.noContent(); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${watchId} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.delete( + { + path: '/api/watcher/watch/{watchId}', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts new file mode 100644 index 0000000000000..f3bce228653fe --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_execute_route.ts @@ -0,0 +1,78 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; + +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { ExecuteDetails } from '../../../models/execute_details'; +// @ts-ignore +import { Watch } from '../../../models/watch'; +// @ts-ignore +import { WatchHistoryItem } from '../../../models/watch_history_item'; + +function executeWatch(callWithRequest: any, executeDetails: any, watchJson: any) { + const body = executeDetails; + body.watch = watchJson; + + return callWithRequest('watcher.executeWatch', { + body, + }); +} + +export function registerExecuteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const executeDetails = ExecuteDetails.fromDownstreamJson(request.body.executeDetails); + const watch = Watch.fromDownstreamJson(request.body.watch); + + try { + const hit = await executeWatch(callWithRequest, executeDetails.upstreamJson, watch.watchJson); + const id = get(hit, '_id'); + const watchHistoryItemJson = get(hit, 'watch_record'); + const watchId = get(hit, 'watch_record.watch_id'); + const json = { + id, + watchId, + watchHistoryItemJson, + includeDetails: true, + }; + + const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); + return response.ok({ + body: { + watchHistoryItem: watchHistoryItem.downstreamJson, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/execute', + validate: { + body: schema.object({ + executeDetails: schema.object({}, { allowUnknowns: true }), + watch: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts new file mode 100644 index 0000000000000..e236d7dd642a3 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_history_route.ts @@ -0,0 +1,97 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; +import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../../common/constants'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { WatchHistoryItem } from '../../../models/watch_history_item'; + +function fetchHistoryItems(callWithRequest: any, watchId: any, startTime: any) { + const params: any = { + index: INDEX_NAMES.WATCHER_HISTORY, + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + body: { + size: ES_SCROLL_SETTINGS.PAGE_SIZE, + sort: [{ 'result.execution_time': 'desc' }], + query: { + bool: { + must: [{ term: { watch_id: watchId } }], + }, + }, + }, + }; + + // Add time range clause to query if startTime is specified + if (startTime !== 'all') { + const timeRangeQuery = { range: { 'result.execution_time': { gte: startTime } } }; + params.body.query.bool.must.push(timeRangeQuery); + } + + return callWithRequest('search', params).then((response: any) => + fetchAllFromScroll(response, callWithRequest) + ); +} + +export function registerHistoryRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { watchId } = request.params; + const { startTime } = request.query; + + try { + const hits = await fetchHistoryItems(callWithRequest, watchId, startTime); + const watchHistoryItems = hits.map((hit: any) => { + const id = get(hit, '_id'); + const watchHistoryItemJson = get(hit, '_source'); + + const opts = { includeDetails: false }; + return WatchHistoryItem.fromUpstreamJson( + { + id, + watchId, + watchHistoryItemJson, + }, + opts + ); + }); + + return response.ok({ + body: { + watchHistoryItems: watchHistoryItems.map( + (watchHistoryItem: any) => watchHistoryItem.downstreamJson + ), + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/watch/{watchId}/history', + validate: { + params: schema.object({ + watchId: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts new file mode 100644 index 0000000000000..7311ad08f73a6 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_load_route.ts @@ -0,0 +1,69 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +// @ts-ignore +import { Watch } from '../../../models/watch'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.getWatch', { + id: watchId, + }); +} + +export function registerLoadRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + const id = request.params.id; + + try { + const hit = await fetchWatch(callWithRequest, id); + const watchJson = get(hit, 'watch'); + const watchStatusJson = get(hit, 'status'); + const json = { + id, + watchJson, + watchStatusJson, + }; + + const watch = Watch.fromUpstreamJson(json, { + throwExceptions: { + Action: false, + }, + }); + return response.ok({ + body: { watch: watch.downstreamJson }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + const body = e.statusCode === 404 ? `Watch with id = ${id} not found` : e; + return response.customError({ statusCode: e.statusCode, body }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + deps.router.get( + { + path: '/api/watcher/watch/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts new file mode 100644 index 0000000000000..5d22392d49ed8 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts @@ -0,0 +1,104 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; +import { WATCH_TYPES } from '../../../../../common/constants'; +import { + serializeJsonWatch, + serializeThresholdWatch, +} from '../../../../../common/lib/serialization'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function fetchWatch(callWithRequest: any, watchId: string) { + return callWithRequest('watcher.getWatch', { + id: watchId, + }); +} + +function saveWatch(callWithRequest: any, id: string, body: any) { + return callWithRequest('watcher.putWatch', { + id, + body, + }); +} + +export function registerSaveRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const { id } = request.params; + const { type, isNew, ...watchConfig } = request.body; + + // For new watches, verify watch with the same ID doesn't already exist + if (isNew) { + try { + const existingWatch = await fetchWatch(callWithRequest, id); + if (existingWatch.found) { + return response.conflict({ + body: { + message: i18n.translate('xpack.watcher.saveRoute.duplicateWatchIdErrorMessage', { + defaultMessage: "There is already a watch with ID '{watchId}'.", + values: { + watchId: id, + }, + }), + }, + }); + } + } catch (e) { + const es404 = isEsError(e) && e.statusCode === 404; + if (!es404) { + return response.internalError({ body: e }); + } + // Else continue... + } + } + + let serializedWatch; + + switch (type) { + case WATCH_TYPES.JSON: + const { name, watch } = watchConfig; + serializedWatch = serializeJsonWatch(name, watch); + break; + + case WATCH_TYPES.THRESHOLD: + serializedWatch = serializeThresholdWatch(watchConfig); + break; + } + + try { + // Create new watch + await saveWatch(callWithRequest, id, serializedWatch); + return response.noContent(); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.put( + { + path: '/api/watcher/watch/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({}, { allowUnknowns: true }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts new file mode 100644 index 0000000000000..d07a264b0b2b1 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_visualize_route.ts @@ -0,0 +1,70 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +// @ts-ignore +import { Watch } from '../../../models/watch'; +// @ts-ignore +import { VisualizeOptions } from '../../../models/visualize_options'; + +function fetchVisualizeData(callWithRequest: any, index: any, body: any) { + const params = { + index, + body, + ignoreUnavailable: true, + allowNoIndices: true, + ignore: [404], + }; + + return callWithRequest('search', params); +} + +export function registerVisualizeRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + const watch = Watch.fromDownstreamJson(request.body.watch); + const options = VisualizeOptions.fromDownstreamJson(request.body.options); + const body = watch.getVisualizeQuery(options); + + try { + const hits = await fetchVisualizeData(callWithRequest, watch.index, body); + const visualizeData = watch.formatVisualizeData(hits); + + return response.ok({ + body: { + visualizeData, + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ statusCode: e.statusCode, body: e }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/watch/visualize', + validate: { + body: schema.object({ + watch: schema.object({}, { allowUnknowns: true }), + options: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts index 8419f6db7f659..5ecbf3e0d2b46 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_watch_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_watch_routes.ts @@ -13,15 +13,16 @@ import { registerActivateRoute } from './register_activate_route'; import { registerDeactivateRoute } from './register_deactivate_route'; import { registerVisualizeRoute } from './register_visualize_route'; import { registerActionRoutes } from './action'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerWatchRoutes(server) { - registerDeleteRoute(server); - registerExecuteRoute(server); - registerLoadRoute(server); - registerSaveRoute(server); - registerHistoryRoute(server); - registerActivateRoute(server); - registerDeactivateRoute(server); - registerActionRoutes(server); - registerVisualizeRoute(server); +export function registerWatchRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerDeleteRoute(deps, legacy); + registerExecuteRoute(deps, legacy); + registerLoadRoute(deps, legacy); + registerSaveRoute(deps, legacy); + registerHistoryRoute(deps, legacy); + registerActivateRoute(deps, legacy); + registerDeactivateRoute(deps, legacy); + registerActionRoutes(deps, legacy); + registerVisualizeRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/index.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/index.ts similarity index 100% rename from x-pack/legacy/plugins/watcher/server/routes/api/watches/index.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/index.ts diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts new file mode 100644 index 0000000000000..29c539a0de138 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_delete_route.ts @@ -0,0 +1,63 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; + +function deleteWatches(callWithRequest: any, watchIds: string[]) { + const deletePromises = watchIds.map(watchId => { + return callWithRequest('watcher.deleteWatch', { + id: watchId, + }) + .then((success: Array<{ _id: string }>) => ({ success })) + .catch((error: Array<{ _id: string }>) => ({ error })); + }); + + return Promise.all(deletePromises).then(results => { + const errors: Error[] = []; + const successes: boolean[] = []; + results.forEach(({ success, error }) => { + if (success) { + successes.push(success._id); + } else if (error) { + errors.push(error._id); + } + }); + + return { + successes, + errors, + }; + }); +} + +export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + try { + const results = await deleteWatches(callWithRequest, request.body.watchIds); + return response.ok({ body: { results } }); + } catch (e) { + return response.internalError({ body: e }); + } + }; + + deps.router.post( + { + path: '/api/watcher/watches/delete', + validate: { + body: schema.object({ + watchIds: schema.arrayOf(schema.string()), + }), + }, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts new file mode 100644 index 0000000000000..b94c29e0f9892 --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_list_route.ts @@ -0,0 +1,86 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { get } from 'lodash'; +import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; +import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; +import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../../common/constants'; +import { isEsError } from '../../../lib/is_es_error'; +import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; +import { RouteDependencies, ServerShim } from '../../../types'; +// @ts-ignore +import { Watch } from '../../../models/watch'; + +function fetchWatches(callWithRequest: any) { + const params = { + index: INDEX_NAMES.WATCHES, + scroll: ES_SCROLL_SETTINGS.KEEPALIVE, + body: { + size: ES_SCROLL_SETTINGS.PAGE_SIZE, + }, + ignore: [404], + }; + + return callWithRequest('search', params).then((response: any) => + fetchAllFromScroll(response, callWithRequest) + ); +} + +export function registerListRoute(deps: RouteDependencies, legacy: ServerShim) { + const handler: RequestHandler = async (ctx, request, response) => { + const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); + + try { + const hits = await fetchWatches(callWithRequest); + const watches = hits.map((hit: any) => { + const id = get(hit, '_id'); + const watchJson = get(hit, '_source'); + const watchStatusJson = get(hit, '_source.status'); + + return Watch.fromUpstreamJson( + { + id, + watchJson, + watchStatusJson, + }, + { + throwExceptions: { + Action: false, + }, + } + ); + }); + + return response.ok({ + body: { + watches: watches.map((watch: any) => watch.downstreamJson), + }, + }); + } catch (e) { + // Case: Error from Elasticsearch JS client + if (isEsError(e)) { + return response.customError({ + statusCode: e.statusCode, + body: { + message: e.message, + }, + }); + } + + // Case: default + return response.internalError({ body: e }); + } + }; + + deps.router.get( + { + path: '/api/watcher/watches', + validate: false, + }, + licensePreRoutingFactory(legacy, handler) + ); +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts similarity index 62% rename from x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js rename to x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts index 5f7ae6a5935bd..dd5f55078e591 100644 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_watches_routes.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watches/register_watches_routes.ts @@ -6,8 +6,9 @@ import { registerListRoute } from './register_list_route'; import { registerDeleteRoute } from './register_delete_route'; +import { RouteDependencies, ServerShim } from '../../../types'; -export function registerWatchesRoutes(server) { - registerListRoute(server); - registerDeleteRoute(server); +export function registerWatchesRoutes(deps: RouteDependencies, legacy: ServerShim) { + registerListRoute(deps, legacy); + registerDeleteRoute(deps, legacy); } diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/types.ts b/x-pack/legacy/plugins/watcher/server/np_ready/types.ts new file mode 100644 index 0000000000000..1b566332befdf --- /dev/null +++ b/x-pack/legacy/plugins/watcher/server/np_ready/types.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 { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'src/core/server'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; + +export interface ServerShim { + route: any; + plugins: { + xpack_main: XPackMainPlugin; + watcher: any; + }; +} + +export interface RouteDependencies { + router: IRouter; + elasticsearchService: ElasticsearchServiceSetup; + elasticsearch: IClusterClient; +} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js deleted file mode 100644 index 7d45d3a2aa60b..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/fields/register_list_route.js +++ /dev/null @@ -1,60 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; -import { Fields } from '../../../models/fields'; - -function fetchFields(callWithRequest, indexes) { - const params = { - index: indexes, - fields: ['*'], - ignoreUnavailable: true, - allowNoIndices: true, - ignore: 404 - }; - - return callWithRequest('fieldCaps', params); -} - -export function registerListRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/fields', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { indexes } = request.payload; - - return fetchFields(callWithRequest, indexes) - .then(response => { - const json = (response.status === 404) - ? { fields: [] } - : response; - - const fields = Fields.fromUpstreamJson(json); - - return fields.downstreamJson; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js b/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js deleted file mode 100644 index 9a66353c742bc..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/index.js +++ /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 { registerHistoryRoutes } from './register_history_routes'; diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js deleted file mode 100644 index 1d34be56fcefc..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/history/register_load_route.js +++ /dev/null @@ -1,78 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError, wrapCustomError } from '../../../lib/error_wrappers'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchHistoryItem(callWithRequest, watchHistoryItemId) { - return callWithRequest('search', { - index: INDEX_NAMES.WATCHER_HISTORY, - body: { - query: { - bool: { - must: [ - { term: { '_id': watchHistoryItemId } }, - ] - } - } - } - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/history/{id}', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const id = request.params.id; - - return fetchHistoryItem(callWithRequest, id) - .then((responseFromES) => { - const hit = get(responseFromES, 'hits.hits[0]'); - if (!hit) { - throw wrapCustomError( - new Error(`Watch History Item with id = ${id} not found`), 404 - ); - } - - const watchHistoryItemJson = get(hit, '_source'); - const watchId = get(hit, '_source.watch_id'); - const json = { - id, - watchId, - watchHistoryItemJson, - includeDetails: true - }; - - const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); - return { - watchHistoryItem: watchHistoryItem.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js deleted file mode 100644 index 86de6f3da7ad5..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/indices/register_get_route.js +++ /dev/null @@ -1,89 +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 { reduce, size } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function getIndexNamesFromAliasesResponse(json) { - return reduce(json, (list, { aliases }, indexName) => { - list.push(indexName); - if (size(aliases) > 0) { - list.push(...Object.keys(aliases)); - } - return list; - }, []); -} - -function getIndices(callWithRequest, pattern, limit = 10) { - return callWithRequest('indices.getAlias', { - index: pattern, - ignore: [404] - }) - .then(aliasResult => { - if (aliasResult.status !== 404) { - const indicesFromAliasResponse = getIndexNamesFromAliasesResponse(aliasResult); - return indicesFromAliasResponse.slice(0, limit); - } - - const params = { - index: pattern, - ignore: [404], - body: { - size: 0, // no hits - aggs: { - indices: { - terms: { - field: '_index', - size: limit, - } - } - } - } - }; - - return callWithRequest('search', params) - .then(response => { - if (response.status === 404 || !response.aggregations) { - return []; - } - return response.aggregations.indices.buckets.map(bucket => bucket.key); - }); - }); -} - -export function registerGetRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/indices', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { pattern } = request.payload; - - return getIndices(callWithRequest, pattern) - .then(indices => { - return { indices }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js deleted file mode 100644 index 65c961c8c82f2..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/settings/register_load_route.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithInternalUserFactory } from '../../../lib/call_with_internal_user_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { Settings } from '../../../models/settings'; - -function fetchClusterSettings(callWithInternalUser) { - return callWithInternalUser('cluster.getSettings', { - includeDefaults: true, - filterPath: '**.xpack.notification' - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - const callWithInternalUser = callWithInternalUserFactory(server); - - server.route({ - path: '/api/watcher/settings', - method: 'GET', - handler: () => { - return fetchClusterSettings(callWithInternalUser) - .then((settings) => { - return Settings.fromUpstreamJson(settings).downstreamJson; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js deleted file mode 100644 index ffecebf805cf6..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/action/register_acknowledge_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../../lib/error_wrappers'; -import { WatchStatus } from '../../../../models/watch_status'; -import { licensePreRoutingFactory } from'../../../../lib/license_pre_routing_factory'; - -export function registerAcknowledgeRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/action/{actionId}/acknowledge', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { watchId, actionId } = request.params; - - return acknowledgeAction(callWithRequest, watchId, actionId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} - -function acknowledgeAction(callWithRequest, watchId, actionId) { - return callWithRequest('watcher.ackWatch', { - id: watchId, - action: actionId - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js deleted file mode 100644 index ea669a16a0172..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_activate_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { WatchStatus } from '../../../models/watch_status'; - -function activateWatch(callWithRequest, watchId) { - return callWithRequest('watcher.activateWatch', { - id: watchId - }); -} - -export function registerActivateRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/activate', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return activateWatch(callWithRequest, watchId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js deleted file mode 100644 index 2411290e2034a..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_deactivate_route.js +++ /dev/null @@ -1,63 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { WatchStatus } from '../../../models/watch_status'; - -function deactivateWatch(callWithRequest, watchId) { - return callWithRequest('watcher.deactivateWatch', { - id: watchId - }); -} - -export function registerDeactivateRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/deactivate', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return deactivateWatch(callWithRequest, watchId) - .then(hit => { - const watchStatusJson = get(hit, 'status'); - const json = { - id: watchId, - watchStatusJson: watchStatusJson - }; - - const watchStatus = WatchStatus.fromUpstreamJson(json); - return { - watchStatus: watchStatus.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js deleted file mode 100644 index dc3b015dffa90..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_delete_route.js +++ /dev/null @@ -1,50 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function deleteWatch(callWithRequest, watchId) { - return callWithRequest('watcher.deleteWatch', { - id: watchId - }); -} - -export function registerDeleteRoute(server) { - - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}', - method: 'DELETE', - handler: (request, h) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { watchId } = request.params; - - return deleteWatch(callWithRequest, watchId) - .then(() => h.response().code(204)) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${watchId} not found` - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js deleted file mode 100644 index f378829147280..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_execute_route.js +++ /dev/null @@ -1,69 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { ExecuteDetails } from '../../../models/execute_details'; -import { Watch } from '../../../models/watch'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function executeWatch(callWithRequest, executeDetails, watchJson) { - const body = executeDetails; - body.watch = watchJson; - - return callWithRequest('watcher.executeWatch', { - body - }); -} - -export function registerExecuteRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/execute', - method: 'PUT', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const executeDetails = ExecuteDetails.fromDownstreamJson(request.payload.executeDetails); - const watch = Watch.fromDownstreamJson(request.payload.watch); - - return executeWatch(callWithRequest, executeDetails.upstreamJson, watch.watchJson) - .then((hit) => { - const id = get(hit, '_id'); - const watchHistoryItemJson = get(hit, 'watch_record'); - const watchId = get(hit, 'watch_record.watch_id'); - const json = { - id, - watchId, - watchHistoryItemJson, - includeDetails: true - }; - - const watchHistoryItem = WatchHistoryItem.fromUpstreamJson(json); - return { - watchHistoryItem: watchHistoryItem.downstreamJson - }; - }) - .catch(err => { - - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js deleted file mode 100644 index 702cf8a2b64e2..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_history_route.js +++ /dev/null @@ -1,89 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { WatchHistoryItem } from '../../../models/watch_history_item'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchHistoryItems(callWithRequest, watchId, startTime) { - const params = { - index: INDEX_NAMES.WATCHER_HISTORY, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - sort: [ - { 'result.execution_time': 'desc' } - ], - query: { - bool: { - must: [ - { term: { 'watch_id': watchId } }, - ] - } - } - } - }; - - // Add time range clause to query if startTime is specified - if (startTime !== 'all') { - const timeRangeQuery = { range: { 'result.execution_time': { gte: startTime } } }; - params.body.query.bool.must.push(timeRangeQuery); - } - - return callWithRequest('search', params) - .then(response => fetchAllFromScroll(response, callWithRequest)); -} - -export function registerHistoryRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{watchId}/history', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { watchId } = request.params; - const { startTime } = request.query; - - return fetchHistoryItems(callWithRequest, watchId, startTime) - .then(hits => { - const watchHistoryItems = hits.map(hit => { - const id = get(hit, '_id'); - const watchHistoryItemJson = get(hit, '_source'); - - const opts = { includeDetails: false }; - return WatchHistoryItem.fromUpstreamJson({ - id, - watchId, - watchHistoryItemJson - }, opts); - }); - - return { - watchHistoryItems: watchHistoryItems.map(watchHistoryItem => watchHistoryItem.downstreamJson) - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js deleted file mode 100644 index e5210dbff3567..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_load_route.js +++ /dev/null @@ -1,68 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { get } from 'lodash'; -import { Watch } from '../../../models/watch'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchWatch(callWithRequest, watchId) { - return callWithRequest('watcher.getWatch', { - id: watchId - }); -} - -export function registerLoadRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{id}', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - const id = request.params.id; - - return fetchWatch(callWithRequest, id) - .then(hit => { - const watchJson = get(hit, 'watch'); - const watchStatusJson = get(hit, 'status'); - const json = { - id, - watchJson, - watchStatusJson, - }; - - const watch = Watch.fromUpstreamJson(json, { - throwExceptions: { - Action: false, - }, - }); - return { - watch: watch.downstreamJson - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - const statusCodeToMessageMap = { - 404: `Watch with id = ${id} not found`, - }; - throw wrapEsError(err, statusCodeToMessageMap); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js deleted file mode 100644 index 3cbb0a4e1cc47..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_save_route.js +++ /dev/null @@ -1,94 +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 { WATCH_TYPES } from '../../../../common/constants'; -import { serializeJsonWatch, serializeThresholdWatch } from '../../../../common/lib/serialization'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError, wrapCustomError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; -import { i18n } from '@kbn/i18n'; - -function fetchWatch(callWithRequest, watchId) { - return callWithRequest('watcher.getWatch', { - id: watchId - }); -} - -function saveWatch(callWithRequest, id, body) { - return callWithRequest('watcher.putWatch', { - id, - body, - }); -} - -export function registerSaveRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/{id}', - method: 'PUT', - handler: async (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const { id, type, isNew, ...watchConfig } = request.payload; - - // For new watches, verify watch with the same ID doesn't already exist - if (isNew) { - const conflictError = wrapCustomError( - new Error(i18n.translate('xpack.watcher.saveRoute.duplicateWatchIdErrorMessage', { - defaultMessage: 'There is already a watch with ID \'{watchId}\'.', - values: { - watchId: id, - } - })), - 409 - ); - - try { - const existingWatch = await fetchWatch(callWithRequest, id); - - if (existingWatch.found) { - throw conflictError; - } - } catch (e) { - // Rethrow conflict error but silently swallow all others - if (e === conflictError) { - throw e; - } - } - } - - let serializedWatch; - - switch (type) { - case WATCH_TYPES.JSON: - const { name, watch } = watchConfig; - serializedWatch = serializeJsonWatch(name, watch); - break; - - case WATCH_TYPES.THRESHOLD: - serializedWatch = serializeThresholdWatch(watchConfig); - break; - } - - // Create new watch - return saveWatch(callWithRequest, id, serializedWatch) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js deleted file mode 100644 index ff9d8f9775d5e..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watch/register_visualize_route.js +++ /dev/null @@ -1,62 +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 { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { Watch } from '../../../models/watch'; -import { VisualizeOptions } from '../../../models/visualize_options'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchVisualizeData(callWithRequest, index, body) { - const params = { - index, - body, - ignoreUnavailable: true, - allowNoIndices: true, - ignore: [404] - }; - - return callWithRequest('search', params); -} - -export function registerVisualizeRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watch/visualize', - method: 'POST', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - const watch = Watch.fromDownstreamJson(request.payload.watch); - const options = VisualizeOptions.fromDownstreamJson(request.payload.options); - const body = watch.getVisualizeQuery(options); - - return fetchVisualizeData(callWithRequest, watch.index, body) - .then(hits => { - const visualizeData = watch.formatVisualizeData(hits); - - return { - visualizeData - }; - }) - .catch(err => { - - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js deleted file mode 100644 index a0bbfb954b755..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_delete_route.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function deleteWatches(callWithRequest, watchIds) { - const deletePromises = watchIds.map(watchId => { - return callWithRequest('watcher.deleteWatch', { - id: watchId, - }) - .then(success => ({ success })) - .catch(error => ({ error })); - }); - - return Promise.all(deletePromises).then(results => { - const errors = []; - const successes = []; - results.forEach(({ success, error }) => { - if (success) { - successes.push(success._id); - } else if (error) { - errors.push(error._id); - } - }); - - return { - successes, - errors, - }; - }); -} - -export function registerDeleteRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watches/delete', - method: 'POST', - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - - try { - const results = await deleteWatches(callWithRequest, request.payload.watchIds); - return { results }; - } catch (err) { - throw wrapUnknownError(err); - } - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js b/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js deleted file mode 100644 index 2a617e275d1ee..0000000000000 --- a/x-pack/legacy/plugins/watcher/server/routes/api/watches/register_list_route.js +++ /dev/null @@ -1,79 +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 { get } from 'lodash'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { fetchAllFromScroll } from '../../../lib/fetch_all_from_scroll'; -import { INDEX_NAMES, ES_SCROLL_SETTINGS } from '../../../../common/constants'; -import { Watch } from '../../../models/watch'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../../lib/error_wrappers'; -import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory'; - -function fetchWatches(callWithRequest) { - const params = { - index: INDEX_NAMES.WATCHES, - scroll: ES_SCROLL_SETTINGS.KEEPALIVE, - body: { - size: ES_SCROLL_SETTINGS.PAGE_SIZE, - }, - ignore: [404] - }; - - return callWithRequest('search', params) - .then(response => fetchAllFromScroll(response, callWithRequest)); -} - -export function registerListRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/watcher/watches', - method: 'GET', - handler: (request) => { - const callWithRequest = callWithRequestFactory(server, request); - - return fetchWatches(callWithRequest) - .then(hits => { - const watches = hits.map(hit => { - const id = get(hit, '_id'); - const watchJson = get(hit, '_source'); - const watchStatusJson = get(hit, '_source.status'); - - return Watch.fromUpstreamJson( - { - id, - watchJson, - watchStatusJson, - }, - { - throwExceptions: { - Action: false, - }, - } - ); - }); - - return { - watches: watches.map(watch => watch.downstreamJson) - }; - }) - .catch(err => { - // Case: Error from Elasticsearch JS client - if (isEsError(err)) { - throw wrapEsError(err); - } - - // Case: default - throw wrapUnknownError(err); - }); - }, - config: { - pre: [ licensePreRouting ] - } - }); -} From 248904ec87ed3dd69b5efd843ef698c3e7410ffe Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 11 Dec 2019 11:05:36 +0100 Subject: [PATCH 52/56] [ML] API integration tests - initial tests for bucket span estimator (#52636) This PR adds basic API integration tests for the bucket span estimator. --- x-pack/test/api_integration/apis/index.js | 1 + .../apis/ml/bucket_span_estimator.ts | 90 +++++++++++++++++++ x-pack/test/api_integration/apis/ml/index.ts | 15 ++++ 3 files changed, 106 insertions(+) create mode 100644 x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts create mode 100644 x-pack/test/api_integration/apis/ml/index.ts diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index ed0e6488320d4..fd700b41df563 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -28,5 +28,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); loadTestFile(require.resolve('./endpoint')); + loadTestFile(require.resolve('./ml')); }); } diff --git a/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts new file mode 100644 index 0000000000000..b5e5168621584 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +const testDataList = [ + { + testTitleSuffix: 'with 1 field, 1 agg, no split', + requestBody: { + aggTypes: ['avg'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['taxless_total_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '15m', ms: 900000 }, + }, + }, + { + testTitleSuffix: 'with 2 fields, 2 aggs, no split', + requestBody: { + aggTypes: ['avg', 'sum'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['products.base_price', 'products.base_unit_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '30m', ms: 1800000 }, + }, + }, + { + testTitleSuffix: 'with 1 field, 1 agg, 1 split with cardinality 46', + requestBody: { + aggTypes: ['avg'], + duration: { start: 1560297859000, end: 1562975136000 }, + fields: ['taxless_total_price'], + index: 'ecommerce', + query: { bool: { must: [{ match_all: {} }] } }, + splitField: 'customer_first_name.keyword', + timeField: 'order_date', + }, + expected: { + responseCode: 200, + responseBody: { name: '3h', ms: 10800000 }, + }, + }, +]; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('bucket span estimator', () => { + before(async () => { + await esArchiver.load('ml/ecommerce'); + }); + + after(async () => { + await esArchiver.unload('ml/ecommerce'); + }); + + for (const testData of testDataList) { + it(`estimates the bucket span ${testData.testTitleSuffix}`, async () => { + const { body } = await supertest + .post('/api/ml/validate/estimate_bucket_span') + .set(COMMON_HEADERS) + .send(testData.requestBody) + .expect(testData.expected.responseCode); + + expect(body).to.eql(testData.expected.responseBody); + }); + } + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts new file mode 100644 index 0000000000000..2e0521e2b8273 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -0,0 +1,15 @@ +/* + * 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'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('Machine Learning', function() { + this.tags(['mlqa']); + + loadTestFile(require.resolve('./bucket_span_estimator')); + }); +} From 9fcc93457f4ac382cbeb40777de369fe50c73eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez?= Date: Wed, 11 Dec 2019 13:47:37 +0100 Subject: [PATCH 53/56] [Logs + Metrics UI] Add missing headers in Logs & metrics (#52405) * Fix broken aria references `EuiDescribedFormGroup` needs an actual header in its `title` for it to make a correct `aria-labelledby`. * Fix `aria-labelledby` references in settings page Co-authored-by: Elastic Machine --- .../fields_configuration_panel.tsx | 50 +++++++++++-------- .../indices_configuration_panel.tsx | 22 ++++---- .../name_configuration_panel.tsx | 4 +- .../analysis_setup_indices_form.tsx | 10 ++-- .../analysis_setup_timerange_form.tsx | 10 ++-- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 771285e8ccee4..5f3d1a63e72eb 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -50,10 +50,12 @@ export const FieldsConfigurationPanel = ({ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ +

    + +

    } description={ Date: Wed, 11 Dec 2019 13:48:40 +0100 Subject: [PATCH 54/56] [Logs + Metrics UI] Remove eslint exceptions (#50979) This removes the two eslint exceptions specific to the `infra` plugin introduced in #49244. fixes #49563 --- .eslintrc.js | 7 -- .../public/components/formatted_time.tsx | 1 - .../log_entry_actions_menu.tsx | 2 +- .../logging/log_highlights_menu.tsx | 24 +++++-- .../logging/log_text_stream/text_styles.tsx | 2 +- .../components/metrics_explorer/metrics.tsx | 33 ++++----- .../components/saved_views/create_modal.tsx | 2 +- .../add_log_column_popover.tsx | 2 +- .../source_configuration_form_state.tsx | 2 +- .../waffle/waffle_inventory_switcher.tsx | 27 ++++--- .../log_analysis_capabilities.tsx | 2 +- .../logs/log_analysis/log_analysis_module.tsx | 10 +-- .../log_analysis/log_analysis_setup_state.tsx | 2 +- .../log_highlights/log_entry_highlights.tsx | 2 +- .../log_highlights/log_summary_highlights.ts | 10 ++- .../logs/log_highlights/next_and_previous.tsx | 2 +- .../logs/log_highlights/redux_bridges.tsx | 6 +- .../containers/logs/with_stream_items.ts | 2 +- .../use_metrics_explorer_data.ts | 3 + .../use_metrics_explorer_options.ts | 2 +- .../infra/public/hooks/use_saved_view.ts | 61 ++++++++-------- .../infra/public/hooks/use_track_metric.tsx | 3 + .../public/pages/infrastructure/index.tsx | 15 ++-- .../infrastructure/metrics_explorer/index.tsx | 7 +- .../use_metric_explorer_state.ts | 12 ++-- .../logs/log_entry_rate/page_content.tsx | 2 +- .../sections/anomalies/table.tsx | 2 +- .../analysis_setup_indices_form.tsx | 2 +- .../use_log_entry_rate_module.tsx | 2 +- .../use_log_entry_rate_results_url_state.tsx | 11 +-- .../metrics/components/chart_section_vis.tsx | 18 ++--- .../metrics/components/node_details_page.tsx | 10 +-- .../pages/metrics/components/section.tsx | 71 ++++++++++--------- .../pages/metrics/components/sub_section.tsx | 36 +++++----- .../metrics/containers/with_metrics_time.tsx | 11 ++- .../infra/public/pages/metrics/index.tsx | 42 +++++------ .../infra/public/utils/cancellable_effect.ts | 3 + .../public/utils/use_kibana_ui_setting.ts | 7 +- .../infra/public/utils/use_tracked_promise.ts | 2 + .../infra/public/utils/use_url_state.ts | 38 +++++++--- .../public/utils/use_visibility_state.ts | 2 +- 41 files changed, 269 insertions(+), 231 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e01632815bc68..367ac892107ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -170,13 +170,6 @@ module.exports = { 'react-hooks/rules-of-hooks': 'off', }, }, - { - files: ['x-pack/legacy/plugins/infra/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - 'react-hooks/rules-of-hooks': 'off', - }, - }, { files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'], rules: { diff --git a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx index 78255c55df124..46b505d4fab52 100644 --- a/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx +++ b/x-pack/legacy/plugins/infra/public/components/formatted_time.tsx @@ -37,7 +37,6 @@ export const useFormattedTime = ( const dateFormat = formatMap[format]; const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [ - getFormattedTime, time, dateFormat, fallbackFormat, diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx index 92c6ddd193609..d018b3a0f38ff 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.tsx @@ -51,7 +51,7 @@ export const LogEntryActionsMenu: React.FunctionComponent<{ /> , ], - [uptimeLink] + [apmLink, uptimeLink] ); const hasMenuItems = useMemo(() => menuItems.length > 0, [menuItems]); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx index 24a5e8bacb4f9..d13ccde7466cd 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_highlights_menu.tsx @@ -16,7 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { debounce } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import euiStyled from '../../../../../common/eui_styled_components'; import { useVisibilityState } from '../../utils/use_visibility_state'; @@ -47,8 +47,25 @@ export const LogHighlightsMenu: React.FC = ({ } = useVisibilityState(false); // Input field state - const [highlightTerm, setHighlightTerm] = useState(''); + const [highlightTerm, _setHighlightTerm] = useState(''); + const debouncedOnChange = useMemo(() => debounce(onChange, 275), [onChange]); + const setHighlightTerm = useCallback( + valueOrUpdater => + _setHighlightTerm(previousHighlightTerm => { + const newHighlightTerm = + typeof valueOrUpdater === 'function' + ? valueOrUpdater(previousHighlightTerm) + : valueOrUpdater; + + if (newHighlightTerm !== previousHighlightTerm) { + debouncedOnChange([newHighlightTerm]); + } + + return newHighlightTerm; + }), + [debouncedOnChange] + ); const changeHighlightTerm = useCallback( e => { const value = e.target.value; @@ -57,9 +74,6 @@ export const LogHighlightsMenu: React.FC = ({ [setHighlightTerm] ); const clearHighlightTerm = useCallback(() => setHighlightTerm(''), [setHighlightTerm]); - useEffect(() => { - debouncedOnChange([highlightTerm]); - }, [highlightTerm]); const button = ( diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx index 1d40c88f5d1d0..e95ac6aa7923b 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx @@ -63,7 +63,7 @@ export const useMeasuredCharacterDimensions = (scale: TextScale) => { X ), - [scale] + [measureElement, scale] ); return { diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx index d59e709d9a19a..42df7c6915a0d 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/metrics.tsx @@ -7,7 +7,7 @@ import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState } from 'react'; import { FieldType } from 'ui/index_patterns'; import { colorTransformer, MetricsExplorerColor } from '../../../common/color_palette'; import { @@ -31,24 +31,19 @@ interface SelectedOption { export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = false }: Props) => { const colors = Object.keys(MetricsExplorerColor) as MetricsExplorerColor[]; - const [inputRef, setInputRef] = useState(null); - const [focusOnce, setFocusState] = useState(false); + const [shouldFocus, setShouldFocus] = useState(autoFocus); - useEffect(() => { - if (inputRef && autoFocus && !focusOnce) { - inputRef.focus(); - setFocusState(true); - } - }, [inputRef]); + // the EuiCombobox forwards the ref to an input element + const autoFocusInputElement = useCallback( + (inputElement: HTMLInputElement | null) => { + if (inputElement && shouldFocus) { + inputElement.focus(); + setShouldFocus(false); + } + }, + [shouldFocus] + ); - // I tried to use useRef originally but the EUIComboBox component's type definition - // would only accept an actual input element or a callback function (with the same type). - // This effectivly does the same thing but is compatible with EuiComboBox. - const handleInputRef = (ref: HTMLInputElement) => { - if (ref) { - setInputRef(ref); - } - }; const handleChange = useCallback( selectedOptions => { onChange( @@ -59,7 +54,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = })) ); }, - [options, onChange] + [onChange, options.aggregation, colors] ); const comboOptions = fields @@ -86,7 +81,7 @@ export const MetricsExplorerMetrics = ({ options, onChange, fields, autoFocus = selectedOptions={selectedOptions} onChange={handleChange} isClearable={true} - inputRef={handleInputRef} + inputRef={autoFocusInputElement} /> ); }; diff --git a/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx b/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx index 8df479f36e2f9..9b8907a1ff9e1 100644 --- a/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx +++ b/x-pack/legacy/plugins/infra/public/components/saved_views/create_modal.tsx @@ -36,7 +36,7 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => { const saveView = useCallback(() => { save(viewName, includeTime); - }, [viewName, includeTime]); + }, [includeTime, save, viewName]); return ( diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx index 9b83f62e7856b..fc8407c5298e6 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/add_log_column_popover.tsx @@ -94,7 +94,7 @@ export const AddLogColumnButtonAndPopover: React.FunctionComponent<{ addLogColumn(selectedOption.columnConfiguration); }, - [addLogColumn, availableColumnOptions] + [addLogColumn, availableColumnOptions, closePopover] ); return ( diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx index 3614a88c1e99e..262649e20709b 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx @@ -52,7 +52,7 @@ export const useSourceConfigurationFormState = (configuration?: SourceConfigurat const resetForm = useCallback(() => { indicesConfigurationFormState.resetForm(); logColumnsConfigurationFormState.resetForm(); - }, [indicesConfigurationFormState.resetForm, logColumnsConfigurationFormState.formState]); + }, [indicesConfigurationFormState, logColumnsConfigurationFormState]); const isFormDirty = useMemo( () => indicesConfigurationFormState.isFormDirty || logColumnsConfigurationFormState.isFormDirty, diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index 38e87038b7c4f..c8f03cef4d6ac 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -17,28 +17,33 @@ import { } from '../../graphql/types'; import { findInventoryModel } from '../../../common/inventory_models'; -interface Props { +interface WaffleInventorySwitcherProps { nodeType: InfraNodeType; changeNodeType: (nodeType: InfraNodeType) => void; changeGroupBy: (groupBy: InfraSnapshotGroupbyInput[]) => void; changeMetric: (metric: InfraSnapshotMetricInput) => void; } -export const WaffleInventorySwitcher = (props: Props) => { +export const WaffleInventorySwitcher: React.FC = ({ + changeNodeType, + changeGroupBy, + changeMetric, + nodeType, +}) => { const [isOpen, setIsOpen] = useState(false); const closePopover = useCallback(() => setIsOpen(false), []); const openPopover = useCallback(() => setIsOpen(true), []); const goToNodeType = useCallback( - (nodeType: InfraNodeType) => { + (targetNodeType: InfraNodeType) => { closePopover(); - props.changeNodeType(nodeType); - props.changeGroupBy([]); - const inventoryModel = findInventoryModel(nodeType); - props.changeMetric({ + changeNodeType(targetNodeType); + changeGroupBy([]); + const inventoryModel = findInventoryModel(targetNodeType); + changeMetric({ type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType, }); }, - [props.changeGroupBy, props.changeNodeType, props.changeMetric] + [closePopover, changeNodeType, changeGroupBy, changeMetric] ); const goToHost = useCallback(() => goToNodeType('host' as InfraNodeType), [goToNodeType]); const goToK8 = useCallback(() => goToNodeType('pod' as InfraNodeType), [goToNodeType]); @@ -68,10 +73,10 @@ export const WaffleInventorySwitcher = (props: Props) => { ], }, ], - [] + [goToDocker, goToHost, goToK8] ); const selectedText = useMemo(() => { - switch (props.nodeType) { + switch (nodeType) { case InfraNodeType.host: return i18n.translate('xpack.infra.waffle.nodeTypeSwitcher.hostsLabel', { defaultMessage: 'Hosts', @@ -81,7 +86,7 @@ export const WaffleInventorySwitcher = (props: Props) => { case InfraNodeType.container: return 'Docker'; } - }, [props.nodeType]); + }, [nodeType]); return ( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index 35a3ac737ada3..bb01043b0db6e 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -46,7 +46,7 @@ export const useLogAnalysisCapabilities = () => { useEffect(() => { fetchMlCapabilities(); - }, []); + }, [fetchMlCapabilities]); const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ fetchMlCapabilitiesRequest.state, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx index 189b58d7923f8..d7d0ecb6f2c8d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx @@ -125,23 +125,23 @@ export const useLogAnalysisModule = ({ dispatchModuleStatus({ type: 'failedSetup' }); }); }, - [cleanUpModule, setUpModule] + [cleanUpModule, dispatchModuleStatus, setUpModule] ); const viewSetupForReconfiguration = useCallback(() => { dispatchModuleStatus({ type: 'requestedJobConfigurationUpdate' }); - }, []); + }, [dispatchModuleStatus]); const viewSetupForUpdate = useCallback(() => { dispatchModuleStatus({ type: 'requestedJobDefinitionUpdate' }); - }, []); + }, [dispatchModuleStatus]); const viewResults = useCallback(() => { dispatchModuleStatus({ type: 'viewedResults' }); - }, []); + }, [dispatchModuleStatus]); const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ - moduleDescriptor.getJobIds, + moduleDescriptor, spaceId, sourceId, ]); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx index 275c0194be3b2..74dbb3c7a8062 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx @@ -140,7 +140,7 @@ export const useAnalysisSetupState = ({ ? [...errors, ...index.errors] : errors; }, []); - }, [selectedIndexNames, validatedIndices, validateIndicesRequest.state]); + }, [isValidating, validateIndicesRequest.state, selectedIndexNames, validatedIndices]); return { cleanupAndSetup, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx index 6ead866fb960a..2b19958a9b1a1 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -78,7 +78,7 @@ export const useLogEntryHighlights = ( } else { setLogEntryHighlights([]); } - }, [highlightTerms, startKey, endKey, filterQuery, sourceVersion]); + }, [endKey, filterQuery, highlightTerms, loadLogEntryHighlights, sourceVersion, startKey]); const logEntryHighlightsById = useMemo( () => diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts index 34c66afda010e..874c70e016496 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts @@ -74,7 +74,15 @@ export const useLogSummaryHighlights = ( } else { setLogSummaryHighlights([]); } - }, [highlightTerms, start, end, bucketSize, filterQuery, sourceVersion]); + }, [ + bucketSize, + debouncedLoadSummaryHighlights, + end, + filterQuery, + highlightTerms, + sourceVersion, + start, + ]); return { logSummaryHighlights, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx index 95ead50119eb4..62a43a5412825 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx @@ -53,7 +53,7 @@ export const useNextAndPrevious = ({ const initialTimeKey = getUniqueLogEntryKey(entries[initialIndex]); setCurrentTimeKey(initialTimeKey); } - }, [currentTimeKey, entries, setCurrentTimeKey]); + }, [currentTimeKey, entries, setCurrentTimeKey, visibleMidpoint]); const indexOfCurrentTimeKey = useMemo(() => { if (currentTimeKey && entries.length > 0) { 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 2b60c6edd97aa..9ea8987d4f326 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 @@ -25,11 +25,11 @@ export const LogHighlightsPositionBridge = withLogPosition( const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context); useEffect(() => { setVisibleMidpoint(visibleMidpoint); - }, [visibleMidpoint]); + }, [setVisibleMidpoint, visibleMidpoint]); useEffect(() => { setJumpToTarget(() => jumpToTargetPosition); - }, [jumpToTargetPosition]); + }, [jumpToTargetPosition, setJumpToTarget]); return null; } @@ -41,7 +41,7 @@ export const LogHighlightsFilterQueryBridge = withLogFilter( useEffect(() => { setFilterQuery(serializedFilterQuery); - }, [serializedFilterQuery]); + }, [serializedFilterQuery, setFilterQuery]); return null; } 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 da468b4391e4e..9b20676486af2 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 @@ -35,7 +35,7 @@ export const WithStreamItems: React.FunctionComponent<{ createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) ), - [logEntries.entries, logEntryHighlightsById] + [isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById] ); return children({ diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts index 1418d6aef67ac..c2a599ea1ae78 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.ts @@ -96,6 +96,9 @@ export function useMetricsExplorerData( } setLoading(false); })(); + + // TODO: fix this dependency list while preserving the semantics + // eslint-disable-next-line react-hooks/exhaustive-deps }, [options, source, timerange, signal, afterKey]); return { error, loading, data }; } diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts index 278f3e0a9c17d..de7a8d5805ecc 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.ts @@ -102,7 +102,7 @@ function useStateWithLocalStorage( const [state, setState] = useState(parseJsonOrDefault(storageState, defaultState)); useEffect(() => { localStorage.setItem(key, JSON.stringify(state)); - }, [state]); + }, [key, state]); return [state, setState]; } diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts b/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts index 8db0ed28d9b21..4b12b6c51ea0e 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts +++ b/x-pack/legacy/plugins/infra/public/hooks/use_saved_view.ts @@ -26,29 +26,32 @@ export const useSavedView = (defaultViewState: ViewState, viewType: s >(viewType); const { create, error: errorOnCreate, createdId } = useCreateSavedObject(viewType); const { deleteObject, deletedId } = useDeleteSavedObject(viewType); - const deleteView = useCallback((id: string) => deleteObject(id), []); + const deleteView = useCallback((id: string) => deleteObject(id), [deleteObject]); const [createError, setCreateError] = useState(null); - useEffect(() => setCreateError(createError), [errorOnCreate, setCreateError]); + useEffect(() => setCreateError(errorOnCreate), [errorOnCreate]); - const saveView = useCallback((d: { [p: string]: any }) => { - const doSave = async () => { - const exists = await hasView(d.name); - if (exists) { - setCreateError( - i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { - defaultMessage: `A view with that name already exists.`, - }) - ); - return; - } - create(d); - }; - setCreateError(null); - doSave(); - }, []); + const saveView = useCallback( + (d: { [p: string]: any }) => { + const doSave = async () => { + const exists = await hasView(d.name); + if (exists) { + setCreateError( + i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', { + defaultMessage: `A view with that name already exists.`, + }) + ); + return; + } + create(d); + }; + setCreateError(null); + doSave(); + }, + [create, hasView] + ); - const savedObjects = data ? data.savedObjects : []; + const savedObjects = useMemo(() => (data ? data.savedObjects : []), [data]); const views = useMemo(() => { const items: Array> = [ { @@ -61,19 +64,17 @@ export const useSavedView = (defaultViewState: ViewState, viewType: s }, ]; - if (data) { - data.savedObjects.forEach( - o => - o.type === viewType && - items.push({ - ...o.attributes, - id: o.id, - }) - ); - } + savedObjects.forEach( + o => + o.type === viewType && + items.push({ + ...o.attributes, + id: o.id, + }) + ); return items; - }, [savedObjects, defaultViewState]); + }, [defaultViewState, savedObjects, viewType]); return { views, diff --git a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx index 379b3af3f1063..c5945ab808202 100644 --- a/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx +++ b/x-pack/legacy/plugins/infra/public/hooks/use_track_metric.tsx @@ -57,6 +57,9 @@ export function useTrackMetric( const trackUiMetric = getTrackerForApp(app); const id = setTimeout(() => trackUiMetric(metricType, decoratedMetric), Math.max(delay, 0)); return () => clearTimeout(id); + + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps }, effectDependencies); } diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx index fe48fcc62f77d..9efbbe790abc1 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx @@ -24,6 +24,7 @@ import { MetricsExplorerPage } from './metrics_explorer'; import { SnapshotPage } from './snapshot'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; +import { SourceLoadingPage } from '../../components/source_loading_page'; interface InfrastructurePageProps extends RouteComponentProps { uiCapabilities: UICapabilities; @@ -95,11 +96,15 @@ export const InfrastructurePage = injectUICapabilities( {({ configuration, createDerivedIndexPattern }) => ( - + {configuration ? ( + + ) : ( + + )} )} diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx index 63f5a81967618..4db4319b91d3c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/index.tsx @@ -11,22 +11,17 @@ import { IIndexPattern } from 'src/plugins/data/public'; import { DocumentTitle } from '../../../components/document_title'; import { MetricsExplorerCharts } from '../../../components/metrics_explorer/charts'; import { MetricsExplorerToolbar } from '../../../components/metrics_explorer/toolbar'; -import { SourceLoadingPage } from '../../../components/source_loading_page'; import { SourceQuery } from '../../../../common/graphql/types'; import { NoData } from '../../../components/empty_states'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { useTrackPageview } from '../../../hooks/use_track_metric'; interface MetricsExplorerPageProps { - source: SourceQuery.Query['source']['configuration'] | undefined; + source: SourceQuery.Query['source']['configuration']; derivedIndexPattern: IIndexPattern; } export const MetricsExplorerPage = ({ source, derivedIndexPattern }: MetricsExplorerPageProps) => { - if (!source) { - return ; - } - const { loading, error, diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts index 415a6ae89a8b1..57ea886169701 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.ts @@ -59,7 +59,7 @@ export const useMetricsExplorerState = ( setAfterKey(null); setTimeRange({ ...currentTimerange, from: start, to: end }); }, - [currentTimerange] + [currentTimerange, setTimeRange] ); const handleGroupByChange = useCallback( @@ -70,7 +70,7 @@ export const useMetricsExplorerState = ( groupBy: groupBy || void 0, }); }, - [options] + [options, setOptions] ); const handleFilterQuerySubmit = useCallback( @@ -81,7 +81,7 @@ export const useMetricsExplorerState = ( filterQuery: query, }); }, - [options] + [options, setOptions] ); const handleMetricsChange = useCallback( @@ -92,7 +92,7 @@ export const useMetricsExplorerState = ( metrics, }); }, - [options] + [options, setOptions] ); const handleAggregationChange = useCallback( @@ -109,7 +109,7 @@ export const useMetricsExplorerState = ( })); setOptions({ ...options, aggregation, metrics }); }, - [options] + [options, setOptions] ); const onViewStateChange = useCallback( @@ -124,7 +124,7 @@ export const useMetricsExplorerState = ( setOptions(vs.options); } }, - [setChartOptions, setTimeRange, setTimeRange] + [setChartOptions, setOptions, setTimeRange] ); return { diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index e62164cb17b2c..e71985f73fbb8 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -36,7 +36,7 @@ export const LogEntryRatePageContent = () => { useEffect(() => { fetchModuleDefinition(); fetchJobStatus(); - }, []); + }, [fetchJobStatus, fetchModuleDefinition]); if (!hasLogAnalysisCapabilites) { return ; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index 2057d75f72354..86760cf2da7d6 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -124,7 +124,7 @@ export const AnomaliesTable: React.FunctionComponent<{ setItemIdToExpandedRowMap(newItemIdToExpandedRowMap); } }, - [results, setTimeRange, timeRange, itemIdToExpandedRowMap, setItemIdToExpandedRowMap] + [itemIdToExpandedRowMap, jobId, results, setTimeRange, timeRange] ); const columns = [ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx index 35cad040323a6..5a4c21670191e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/setup/initial_configuration_step/analysis_setup_indices_form.tsx @@ -54,7 +54,7 @@ export const AnalysisSetupIndicesForm: React.FunctionComponent<{
    ); }), - [indices] + [handleCheckboxChange, indices] ); return ( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_module.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_module.tsx index ab6a6578601bf..d1efedb176aba 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_module.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_module.tsx @@ -31,7 +31,7 @@ export const useLogEntryRateModule = ({ spaceId, timestampField, }), - [indexPattern] + [indexPattern, sourceId, spaceId, timestampField] ); return useLogAnalysisModule({ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx index 017be6be49e16..6d4495c8d9e0f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx @@ -8,7 +8,6 @@ import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import * as rt from 'io-ts'; -import { useEffect } from 'react'; import { useUrlState } from '../../../utils/use_url_state'; @@ -41,12 +40,9 @@ export const useLogAnalysisResultsUrlState = () => { pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)), encodeUrlState: urlTimeRangeRT.encode, urlStateKey: TIME_RANGE_URL_STATE_KEY, + writeDefaultState: true, }); - useEffect(() => { - setTimeRange(timeRange); - }, []); - const [autoRefresh, setAutoRefresh] = useUrlState({ defaultState: { isPaused: false, @@ -56,12 +52,9 @@ export const useLogAnalysisResultsUrlState = () => { pipe(autoRefreshRT.decode(value), fold(constant(undefined), identity)), encodeUrlState: autoRefreshRT.encode, urlStateKey: AUTOREFRESH_URL_STATE_KEY, + writeDefaultState: true, }); - useEffect(() => { - setAutoRefresh(autoRefresh); - }, []); - return { timeRange, setTimeRange, diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx index 425b5a43f793f..309961cc39025 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import { @@ -42,15 +42,15 @@ export const ChartSectionVis = ({ seriesOverrides, type, }: VisSectionProps) => { - if (!metric || !id) { - return null; - } const [dateFormat] = useKibanaUiSetting('dateFormat'); const valueFormatter = useCallback(getFormatter(formatter, formatterTemplate), [ formatter, formatterTemplate, ]); - const dateFormatter = useCallback(niceTimeFormatter(getMaxMinTimestamp(metric)), [metric]); + const dateFormatter = useMemo( + () => (metric != null ? niceTimeFormatter(getMaxMinTimestamp(metric)) : undefined), + [metric] + ); const handleTimeChange = useCallback( (from: number, to: number) => { if (onChangeRangeTime) { @@ -73,7 +73,9 @@ export const ChartSectionVis = ({ ), }; - if (!metric) { + if (!id) { + return null; + } else if (!metric) { return ( ); - } - - if (metric.series.some(seriesHasLessThen2DataPoints)) { + } else if (metric.series.some(seriesHasLessThen2DataPoints)) { return ( { - if (!props.metadata) { - return null; - } - const { parsedTimeRange } = props; const { metrics, loading, makeRequest, error } = useNodeDetails( props.requiredMetrics, @@ -65,11 +61,11 @@ export const NodeDetailsPage = (props: Props) => { const refetch = useCallback(() => { makeRequest(); - }, []); + }, [makeRequest]); useEffect(() => { makeRequest(); - }, [parsedTimeRange]); + }, [makeRequest, parsedTimeRange]); if (error) { return ; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx index 32d2e2eff8ab9..2f9ed9f54df82 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/section.tsx @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiTitle } from '@elastic/eui'; import React, { - useContext, Children, - isValidElement, cloneElement, FunctionComponent, - useMemo, + isValidElement, + useContext, } from 'react'; -import { EuiTitle } from '@elastic/eui'; + import { SideNavContext, SubNavItem } from '../lib/side_nav_context'; import { LayoutProps } from '../types'; @@ -31,35 +31,42 @@ export const Section: FunctionComponent = ({ stopLiveStreaming, }) => { const { addNavItem } = useContext(SideNavContext); - const subNavItems: SubNavItem[] = []; - const childrenWithProps = useMemo( - () => - Children.map(children, child => { - if (isValidElement(child)) { - const metric = (metrics && metrics.find(m => m.id === child.props.id)) || null; - if (metric) { - subNavItems.push({ - id: child.props.id, - name: child.props.label, - onClick: () => { - const el = document.getElementById(child.props.id); - if (el) { - el.scrollIntoView(); - } - }, - }); - } - return cloneElement(child, { - metrics, - onChangeRangeTime, - isLiveStreaming, - stopLiveStreaming, - }); - } - return null; - }), - [children, metrics, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] + const subNavItems = Children.toArray(children).reduce( + (accumulatedChildren, child) => { + if (!isValidElement(child)) { + return accumulatedChildren; + } + const metric = metrics?.find(m => m.id === child.props.id) ?? null; + if (metric === null) { + return accumulatedChildren; + } + return [ + ...accumulatedChildren, + { + id: child.props.id, + name: child.props.label, + onClick: () => { + const el = document.getElementById(child.props.id); + if (el) { + el.scrollIntoView(); + } + }, + }, + ]; + }, + [] + ); + + const childrenWithProps = Children.map(children, child => + isValidElement(child) + ? cloneElement(child, { + metrics, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }) + : null ); if (metrics && subNavItems.length) { diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx index f3db3b1670199..325d510293135 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/sub_section.tsx @@ -23,29 +23,25 @@ export const SubSection: FunctionComponent = ({ isLiveStreaming, stopLiveStreaming, }) => { - if (!children || !metrics) { + const metric = useMemo(() => metrics?.find(m => m.id === id), [id, metrics]); + + if (!children || !metric) { return null; } - const metric = metrics.find(m => m.id === id); - if (!metric) { + + const childrenWithProps = Children.map(children, child => { + if (isValidElement(child)) { + return cloneElement(child, { + metric, + id, + onChangeRangeTime, + isLiveStreaming, + stopLiveStreaming, + }); + } return null; - } - const childrenWithProps = useMemo( - () => - Children.map(children, child => { - if (isValidElement(child)) { - return cloneElement(child, { - metric, - id, - onChangeRangeTime, - isLiveStreaming, - stopLiveStreaming, - }); - } - return null; - }), - [children, metric, id, onChangeRangeTime, isLiveStreaming, stopLiveStreaming] - ); + }); + return (
    {label ? ( diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx index 432725b6f62b0..64d2ddb67139d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx @@ -59,13 +59,10 @@ export const useMetricsTime = () => { const [parsedTimeRange, setParsedTimeRange] = useState(parseRange(defaultRange)); - const updateTimeRange = useCallback( - (range: MetricsTimeInput) => { - setTimeRange(range); - setParsedTimeRange(parseRange(range)); - }, - [setParsedTimeRange] - ); + const updateTimeRange = useCallback((range: MetricsTimeInput) => { + setTimeRange(range); + setParsedTimeRange(parseRange(range)); + }, []); return { timeRange, diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx index 93253406aec2d..b330ad02f1022 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx @@ -112,26 +112,28 @@ export const MetricDetail = withMetricPageProviders( })} /> - + {metadata ? ( + + ) : null} )} diff --git a/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts b/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts index bb7d253ea1557..a986af07f0c9a 100644 --- a/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts +++ b/x-pack/legacy/plugins/infra/public/utils/cancellable_effect.ts @@ -27,5 +27,8 @@ export const useCancellableEffect = ( effect(() => cancellationSignal.isCancelled); return cancellationSignal.cancel; + + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts index c48f95a6521cf..1b08fb4231243 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_ui_setting.ts @@ -28,10 +28,15 @@ import { useObservable } from './use_observable'; export const useKibanaUiSetting = (key: string, defaultValue?: any) => { const uiSettingsClient = npSetup.core.uiSettings; - const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [uiSettingsClient]); + const uiSetting$ = useMemo(() => uiSettingsClient.get$(key, defaultValue), [ + defaultValue, + key, + uiSettingsClient, + ]); const uiSetting = useObservable(uiSetting$); const setUiSetting = useCallback((value: any) => uiSettingsClient.set(key, value), [ + key, uiSettingsClient, ]); diff --git a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts index 366caf0dfb156..c23bab7026aaa 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts @@ -190,6 +190,8 @@ export const useTrackedPromise = ( return newPendingPromise.promise; }, + // the dependencies are managed by the caller + // eslint-disable-next-line react-hooks/exhaustive-deps dependencies ); diff --git a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts index d03a5aaa9d697..79a5d552bcd78 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts @@ -5,10 +5,10 @@ */ import { Location } from 'history'; -import { useMemo, useCallback } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { decode, encode, RisonValue } from 'rison-node'; - import { QueryString } from 'ui/utils/query_string'; + import { useHistory } from './history_context'; export const useUrlState = ({ @@ -16,21 +16,26 @@ export const useUrlState = ({ decodeUrlState, encodeUrlState, urlStateKey, + writeDefaultState = false, }: { defaultState: State; decodeUrlState: (value: RisonValue | undefined) => State | undefined; encodeUrlState: (value: State) => RisonValue | undefined; urlStateKey: string; + writeDefaultState?: boolean; }) => { const history = useHistory(); + // history.location is mutable so we can't reliably use useMemo + const queryString = history?.location ? getQueryStringFromLocation(history.location) : ''; + const urlStateString = useMemo(() => { - if (!history) { + if (!queryString) { return; } - return getParamFromQueryString(getQueryStringFromLocation(history.location), urlStateKey); - }, [history && history.location, urlStateKey]); + return getParamFromQueryString(queryString, urlStateKey); + }, [queryString, urlStateKey]); const decodedState = useMemo(() => decodeUrlState(decodeRisonUrlState(urlStateString)), [ decodeUrlState, @@ -44,27 +49,38 @@ export const useUrlState = ({ const setState = useCallback( (newState: State | undefined) => { - if (!history) { + if (!history || !history.location) { return; } - const location = history.location; + const currentLocation = history.location; const newLocation = replaceQueryStringInLocation( - location, + currentLocation, replaceStateKeyInQueryString( urlStateKey, typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined - )(getQueryStringFromLocation(location)) + )(getQueryStringFromLocation(currentLocation)) ); - if (newLocation !== location) { + if (newLocation !== currentLocation) { history.replace(newLocation); } }, - [encodeUrlState, history, history && history.location, urlStateKey] + [encodeUrlState, history, urlStateKey] ); + const [shouldInitialize, setShouldInitialize] = useState( + writeDefaultState && typeof decodedState === 'undefined' + ); + + useEffect(() => { + if (shouldInitialize) { + setShouldInitialize(false); + setState(defaultState); + } + }, [shouldInitialize, setState, defaultState]); + return [state, setState] as [typeof state, typeof setState]; }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts b/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts index 5763834b1cc2a..f4d8b572e4f7f 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_visibility_state.ts @@ -20,6 +20,6 @@ export const useVisibilityState = (initialState: boolean) => { show, toggle, }), - [isVisible, show, hide] + [hide, isVisible, show, toggle] ); }; From 489b39cfe7cc88dfc2ba77d2f8af93fdf08c36db Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 11 Dec 2019 14:14:25 +0100 Subject: [PATCH 55/56] Re-enable datemath in from/to canvas timelion args (#52159) --- .../canvas/public/functions/timelion.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts index ee7dd981009d9..4377f2cb4d53b 100644 --- a/x-pack/legacy/plugins/canvas/public/functions/timelion.ts +++ b/x-pack/legacy/plugins/canvas/public/functions/timelion.ts @@ -5,7 +5,10 @@ */ import { flatten } from 'lodash'; +import moment from 'moment-timezone'; import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; +import { TimeRange } from 'src/plugins/data/common'; import { ExpressionFunction, DatatableRow } from 'src/plugins/expressions/public'; import { fetch } from '../../common/lib/fetch'; // @ts-ignore untyped local @@ -21,6 +24,26 @@ interface Arguments { timezone: string; } +/** + * This function parses a given time range containing date math + * and returns ISO dates. Parsing is done respecting the given time zone. + * @param timeRange time range to parse + * @param timeZone time zone to do the parsing in + */ +function parseDateMath(timeRange: TimeRange, timeZone: string) { + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeZone); + + const parsedRange = npStart.plugins.data.query.timefilter.timefilter.calculateBounds(timeRange); + + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + + return parsedRange; +} + export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Promise> { const { help, args: argHelp } = getFunctionHelp().timelion; @@ -64,8 +87,8 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr // workpad, if it exists. Otherwise fall back on the function args. const timeFilter = context.and.find(and => and.type === 'time'); const range = timeFilter - ? { from: timeFilter.from, to: timeFilter.to } - : { from: args.from, to: args.to }; + ? { min: timeFilter.from, max: timeFilter.to } + : parseDateMath({ from: args.from, to: args.to }, args.timezone); const body = { extended: { @@ -79,8 +102,8 @@ export function timelion(): ExpressionFunction<'timelion', Filter, Arguments, Pr }, sheet: [args.query], time: { - from: range.from, - to: range.to, + from: range.min, + to: range.max, interval: args.interval, timezone: args.timezone, }, From b6ea6990c0b679aa890ff87b161f78485f3b7200 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 11 Dec 2019 14:19:28 +0100 Subject: [PATCH 56/56] Migrate url shortener service (#50896) --- .github/CODEOWNERS | 4 +- .../url_shortening/routes/create_routes.js | 2 - .../server/url_shortening/routes/goto.js | 8 +- .../routes/lib/short_url_lookup.js | 24 ---- .../routes/lib/short_url_lookup.test.js | 37 ------ src/plugins/share/kibana.json | 2 +- .../share/server/index.ts} | 20 +-- src/plugins/share/server/plugin.ts | 37 ++++++ .../share/server/routes/create_routes.ts | 32 +++++ src/plugins/share/server/routes/goto.ts | 64 +++++++++ .../routes/lib/short_url_assert_valid.test.ts | 63 +++++++++ .../routes/lib/short_url_assert_valid.ts | 41 ++++++ .../routes/lib/short_url_lookup.test.ts | 125 ++++++++++++++++++ .../server/routes/lib/short_url_lookup.ts | 84 ++++++++++++ .../share/server/routes/shorten_url.ts | 48 +++++++ .../apis/short_urls/feature_controls.ts | 2 +- 16 files changed, 504 insertions(+), 89 deletions(-) rename src/{legacy/server/url_shortening/routes/shorten_url.js => plugins/share/server/index.ts} (60%) create mode 100644 src/plugins/share/server/plugin.ts create mode 100644 src/plugins/share/server/routes/create_routes.ts create mode 100644 src/plugins/share/server/routes/goto.ts create mode 100644 src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts create mode 100644 src/plugins/share/server/routes/lib/short_url_assert_valid.ts create mode 100644 src/plugins/share/server/routes/lib/short_url_lookup.test.ts create mode 100644 src/plugins/share/server/routes/lib/short_url_lookup.ts create mode 100644 src/plugins/share/server/routes/shorten_url.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c5e6768c17d46..338fbf2e359b7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,8 @@ # App /x-pack/legacy/plugins/lens/ @elastic/kibana-app /x-pack/legacy/plugins/graph/ @elastic/kibana-app +/src/plugins/share/ @elastic/kibana-app +/src/legacy/server/url_shortening/ @elastic/kibana-app /src/legacy/server/sample_data/ @elastic/kibana-app # App Architecture @@ -14,7 +16,6 @@ /src/plugins/kibana_react/ @elastic/kibana-app-arch /src/plugins/kibana_utils/ @elastic/kibana-app-arch /src/plugins/navigation/ @elastic/kibana-app-arch -/src/plugins/share/ @elastic/kibana-app-arch /src/plugins/ui_actions/ @elastic/kibana-app-arch /src/plugins/visualizations/ @elastic/kibana-app-arch /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch @@ -28,7 +29,6 @@ /src/legacy/core_plugins/kibana/server/routes/api/suggestions/ @elastic/kibana-app-arch /src/legacy/core_plugins/visualizations/ @elastic/kibana-app-arch /src/legacy/server/index_patterns/ @elastic/kibana-app-arch -/src/legacy/server/url_shortening/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui diff --git a/src/legacy/server/url_shortening/routes/create_routes.js b/src/legacy/server/url_shortening/routes/create_routes.js index 091eabcf47c1f..c6347ace873f7 100644 --- a/src/legacy/server/url_shortening/routes/create_routes.js +++ b/src/legacy/server/url_shortening/routes/create_routes.js @@ -19,12 +19,10 @@ import { shortUrlLookupProvider } from './lib/short_url_lookup'; import { createGotoRoute } from './goto'; -import { createShortenUrlRoute } from './shorten_url'; export function createRoutes(server) { const shortUrlLookup = shortUrlLookupProvider(server); server.route(createGotoRoute({ server, shortUrlLookup })); - server.route(createShortenUrlRoute({ shortUrlLookup })); } diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/legacy/server/url_shortening/routes/goto.js index 675bc5df50670..60a34499dd2d5 100644 --- a/src/legacy/server/url_shortening/routes/goto.js +++ b/src/legacy/server/url_shortening/routes/goto.js @@ -22,18 +22,12 @@ import { shortUrlAssertValid } from './lib/short_url_assert_valid'; export const createGotoRoute = ({ server, shortUrlLookup }) => ({ method: 'GET', - path: '/goto/{urlId}', + path: '/goto_LP/{urlId}', handler: async function (request, h) { try { const url = await shortUrlLookup.getUrl(request.params.urlId, request); shortUrlAssertValid(url); - const uiSettings = request.getUiSettingsService(); - const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage'); - if (!stateStoreInSessionStorage) { - return h.redirect(request.getBasePath() + url); - } - const app = server.getHiddenUiAppById('stateSessionStorageRedirect'); return h.renderApp(app, { redirectUrl: url, diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js index c4f6af03d7d93..3a4b96c802c58 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js +++ b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js @@ -17,7 +17,6 @@ * under the License. */ -import crypto from 'crypto'; import { get } from 'lodash'; export function shortUrlLookupProvider(server) { @@ -34,29 +33,6 @@ export function shortUrlLookupProvider(server) { } return { - async generateUrlId(url, req) { - const id = crypto.createHash('md5').update(url).digest('hex'); - const savedObjectsClient = req.getSavedObjectsClient(); - const { isConflictError } = savedObjectsClient.errors; - - try { - const doc = await savedObjectsClient.create('url', { - url, - accessCount: 0, - createDate: new Date(), - accessDate: new Date() - }, { id }); - - return doc.id; - } catch (error) { - if (isConflictError(error)) { - return id; - } - - throw error; - } - }, - async getUrl(id, req) { const doc = await req.getSavedObjectsClient().get('url', id); updateMetadata(doc, req); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js index 033aeb92926a5..7303682c63e0b 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js +++ b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js @@ -48,43 +48,6 @@ describe('shortUrlLookupProvider', () => { sandbox.restore(); }); - describe('generateUrlId', () => { - it('returns the document id', async () => { - const id = await shortUrl.generateUrlId(URL, req); - expect(id).toEqual(ID); - }); - - it('provides correct arguments to savedObjectsClient', async () => { - await shortUrl.generateUrlId(URL, req); - - sinon.assert.calledOnce(savedObjectsClient.create); - const [type, attributes, options] = savedObjectsClient.create.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate', 'createDate', 'url']); - expect(attributes.url).toEqual(URL); - expect(options.id).toEqual(ID); - }); - - it('passes persists attributes', async () => { - await shortUrl.generateUrlId(URL, req); - - sinon.assert.calledOnce(savedObjectsClient.create); - const [type, attributes] = savedObjectsClient.create.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate', 'createDate', 'url']); - expect(attributes.url).toEqual(URL); - }); - - it('gracefully handles version conflict', async () => { - const error = savedObjectsClient.errors.decorateConflictError(new Error()); - savedObjectsClient.create.throws(error); - const id = await shortUrl.generateUrlId(URL, req); - expect(id).toEqual(ID); - }); - }); - describe('getUrl', () => { beforeEach(() => { const attributes = { accessCount: 2, url: URL }; diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json index bbe393a76c5da..dce2ac9281aba 100644 --- a/src/plugins/share/kibana.json +++ b/src/plugins/share/kibana.json @@ -1,6 +1,6 @@ { "id": "share", "version": "kibana", - "server": false, + "server": true, "ui": true } diff --git a/src/legacy/server/url_shortening/routes/shorten_url.js b/src/plugins/share/server/index.ts similarity index 60% rename from src/legacy/server/url_shortening/routes/shorten_url.js rename to src/plugins/share/server/index.ts index 0203e9373384a..9e574314f8000 100644 --- a/src/legacy/server/url_shortening/routes/shorten_url.js +++ b/src/plugins/share/server/index.ts @@ -17,19 +17,9 @@ * under the License. */ -import { handleShortUrlError } from './lib/short_url_error'; -import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { PluginInitializerContext } from '../../../core/server'; +import { SharePlugin } from './plugin'; -export const createShortenUrlRoute = ({ shortUrlLookup }) => ({ - method: 'POST', - path: '/api/shorten_url', - handler: async function (request) { - try { - shortUrlAssertValid(request.payload.url); - const urlId = await shortUrlLookup.generateUrlId(request.payload.url, request); - return { urlId }; - } catch (err) { - throw handleShortUrlError(err); - } - } -}); +export function plugin(initializerContext: PluginInitializerContext) { + return new SharePlugin(initializerContext); +} diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts new file mode 100644 index 0000000000000..bcb681a50652a --- /dev/null +++ b/src/plugins/share/server/plugin.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 { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { createRoutes } from './routes/create_routes'; + +export class SharePlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup(core: CoreSetup) { + createRoutes(core, this.initializerContext.logger.get()); + } + + public start() { + this.initializerContext.logger.get().debug('Starting plugin'); + } + + public stop() { + this.initializerContext.logger.get().debug('Stopping plugin'); + } +} diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts new file mode 100644 index 0000000000000..bd4b6fdb08791 --- /dev/null +++ b/src/plugins/share/server/routes/create_routes.ts @@ -0,0 +1,32 @@ +/* + * 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 { CoreSetup, Logger } from 'kibana/server'; + +import { shortUrlLookupProvider } from './lib/short_url_lookup'; +import { createGotoRoute } from './goto'; +import { createShortenUrlRoute } from './shorten_url'; + +export function createRoutes({ http }: CoreSetup, logger: Logger) { + const shortUrlLookup = shortUrlLookupProvider({ logger }); + const router = http.createRouter(); + + createGotoRoute({ router, shortUrlLookup, http }); + createShortenUrlRoute({ router, shortUrlLookup }); +} diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts new file mode 100644 index 0000000000000..7343dc1bd34a2 --- /dev/null +++ b/src/plugins/share/server/routes/goto.ts @@ -0,0 +1,64 @@ +/* + * 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 { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createGotoRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: '/goto/{urlId}', + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); + shortUrlAssertValid(url); + + const uiSettings = context.core.uiSettings.client; + const stateStoreInSessionStorage = await uiSettings.get('state:storeInSessionStorage'); + if (!stateStoreInSessionStorage) { + return response.redirected({ + headers: { + location: http.basePath.prepend(url), + }, + }); + } + return response.redirected({ + headers: { + location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + }, + }); + }) + ); +}; diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts new file mode 100644 index 0000000000000..f83073e6aefe9 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { shortUrlAssertValid } from './short_url_assert_valid'; + +describe('shortUrlAssertValid()', () => { + const invalid = [ + ['protocol', 'http://localhost:5601/app/kibana'], + ['protocol', 'https://localhost:5601/app/kibana'], + ['protocol', 'mailto:foo@bar.net'], + ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url + ['hostname', 'localhost/app/kibana'], + ['hostname and port', 'local.host:5601/app/kibana'], + ['hostname and auth', 'user:pass@localhost.net/app/kibana'], + ['path traversal', '/app/../../not-kibana'], + ['deep path', '/app/kibana/foo'], + ['deep path', '/app/kibana/foo/bar'], + ['base path', '/base/app/kibana'], + ]; + + invalid.forEach(([desc, url]) => { + it(`fails when url has ${desc}`, () => { + try { + shortUrlAssertValid(url); + throw new Error(`expected assertion to throw`); + } catch (err) { + if (!err || !err.isBoom) { + throw err; + } + } + }); + }); + + const valid = [ + '/app/kibana', + '/app/monitoring#angular/route', + '/app/text#document-id', + '/app/some?with=query', + '/app/some?with=query#and-a-hash', + ]; + + valid.forEach(url => { + it(`allows ${url}`, () => { + shortUrlAssertValid(url); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_assert_valid.ts b/src/plugins/share/server/routes/lib/short_url_assert_valid.ts new file mode 100644 index 0000000000000..2f120bbc03cd7 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_assert_valid.ts @@ -0,0 +1,41 @@ +/* + * 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 { parse } from 'url'; +import { trim } from 'lodash'; +import Boom from 'boom'; + +export function shortUrlAssertValid(url: string) { + const { protocol, hostname, pathname } = parse(url); + + if (protocol) { + throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); + } + + if (hostname) { + throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); + } + + const pathnameParts = trim(pathname, '/').split('/'); + if (pathnameParts.length !== 2) { + throw Boom.notAcceptable( + `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` + ); + } +} diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts new file mode 100644 index 0000000000000..87e2b7b726e59 --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { shortUrlLookupProvider, ShortUrlLookupService } from './short_url_lookup'; +import { SavedObjectsClientContract, Logger } from 'kibana/server'; +import { SavedObjectsClient } from '../../../../../core/server'; + +describe('shortUrlLookupProvider', () => { + const ID = 'bf00ad16941fc51420f91a93428b27a0'; + const TYPE = 'url'; + const URL = 'http://elastic.co'; + + let savedObjects: jest.Mocked; + let deps: { savedObjects: SavedObjectsClientContract }; + let shortUrl: ShortUrlLookupService; + + beforeEach(() => { + savedObjects = ({ + get: jest.fn(), + create: jest.fn(() => Promise.resolve({ id: ID })), + update: jest.fn(), + errors: SavedObjectsClient.errors, + } as unknown) as jest.Mocked; + + deps = { savedObjects }; + shortUrl = shortUrlLookupProvider({ logger: ({ warn: () => {} } as unknown) as Logger }); + }); + + describe('generateUrlId', () => { + it('returns the document id', async () => { + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + + it('provides correct arguments to savedObjectsClient', async () => { + await shortUrl.generateUrlId(URL, { savedObjects }); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes, options] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + expect(options!.id).toEqual(ID); + }); + + it('passes persists attributes', async () => { + await shortUrl.generateUrlId(URL, deps); + + expect(savedObjects.create).toHaveBeenCalledTimes(1); + const [type, attributes] = savedObjects.create.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(Object.keys(attributes).sort()).toEqual([ + 'accessCount', + 'accessDate', + 'createDate', + 'url', + ]); + expect(attributes.url).toEqual(URL); + }); + + it('gracefully handles version conflict', async () => { + const error = savedObjects.errors.decorateConflictError(new Error()); + savedObjects.create.mockImplementation(() => { + throw error; + }); + const id = await shortUrl.generateUrlId(URL, deps); + expect(id).toEqual(ID); + }); + }); + + describe('getUrl', () => { + beforeEach(() => { + const attributes = { accessCount: 2, url: URL }; + savedObjects.get.mockResolvedValue({ id: ID, attributes, type: 'url', references: [] }); + }); + + it('provides the ID to savedObjectsClient', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.get).toHaveBeenCalledTimes(1); + expect(savedObjects.get).toHaveBeenCalledWith(TYPE, ID); + }); + + it('returns the url', async () => { + const response = await shortUrl.getUrl(ID, deps); + expect(response).toEqual(URL); + }); + + it('increments accessCount', async () => { + await shortUrl.getUrl(ID, { savedObjects }); + + expect(savedObjects.update).toHaveBeenCalledTimes(1); + + const [type, id, attributes] = savedObjects.update.mock.calls[0]; + + expect(type).toEqual(TYPE); + expect(id).toEqual(ID); + expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); + expect(attributes.accessCount).toEqual(3); + }); + }); +}); diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.ts b/src/plugins/share/server/routes/lib/short_url_lookup.ts new file mode 100644 index 0000000000000..0d8a9c86621de --- /dev/null +++ b/src/plugins/share/server/routes/lib/short_url_lookup.ts @@ -0,0 +1,84 @@ +/* + * 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 crypto from 'crypto'; +import { get } from 'lodash'; + +import { Logger, SavedObject, SavedObjectsClientContract } from 'kibana/server'; + +export interface ShortUrlLookupService { + generateUrlId(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; + getUrl(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; +} + +export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrlLookupService { + async function updateMetadata( + doc: SavedObject, + { savedObjects }: { savedObjects: SavedObjectsClientContract } + ) { + try { + await savedObjects.update('url', doc.id, { + accessDate: new Date().valueOf(), + accessCount: get(doc, 'attributes.accessCount', 0) + 1, + }); + } catch (error) { + logger.warn('Warning: Error updating url metadata'); + logger.warn(error); + // swallow errors. It isn't critical if there is no update. + } + } + + return { + async generateUrlId(url, { savedObjects }) { + const id = crypto + .createHash('md5') + .update(url) + .digest('hex'); + const { isConflictError } = savedObjects.errors; + + try { + const doc = await savedObjects.create( + 'url', + { + url, + accessCount: 0, + createDate: new Date().valueOf(), + accessDate: new Date().valueOf(), + }, + { id } + ); + + return doc.id; + } catch (error) { + if (isConflictError(error)) { + return id; + } + + throw error; + } + }, + + async getUrl(id, { savedObjects }) { + const doc = await savedObjects.get('url', id); + updateMetadata(doc, { savedObjects }); + + return doc.attributes.url; + }, + }; +} diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts new file mode 100644 index 0000000000000..116b90c6971c5 --- /dev/null +++ b/src/plugins/share/server/routes/shorten_url.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + +import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; + +export const createShortenUrlRoute = ({ + shortUrlLookup, + router, +}: { + shortUrlLookup: ShortUrlLookupService; + router: IRouter; +}) => { + router.post( + { + path: '/api/shorten_url', + validate: { + body: schema.object({ url: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + shortUrlAssertValid(request.body.url); + const urlId = await shortUrlLookup.generateUrlId(request.body.url, { + savedObjects: context.core.savedObjects.client, + }); + return response.ok({ body: { urlId } }); + }) + ); +}; diff --git a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts index 06fd971399ea3..db5e11ef367ad 100644 --- a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts +++ b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts @@ -107,7 +107,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) expect(resp.status).to.eql(302); expect(resp.headers.location).to.eql('/app/kibana#foo/bar/baz'); } else { - expect(resp.status).to.eql(500); + expect(resp.status).to.eql(403); expect(resp.headers.location).to.eql(undefined); } });