From aeba68464e087cea6bb5d83e8d0e2eaae647c048 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 2 Sep 2020 17:23:45 +0200 Subject: [PATCH 01/43] Bump buffer from v5.2.1 to v5.6.0 (#76478) --- yarn.lock | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3be2599c64201..9ab1de22af57c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7791,15 +7791,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.1.0, buffer@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" - integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -buffer@^5.5.0: +buffer@^5.1.0, buffer@^5.2.0, buffer@^5.5.0: version "5.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== From 46689b33488fa76e17b5f2d0028d483af5833cf3 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 2 Sep 2020 17:24:12 +0200 Subject: [PATCH 02/43] Bump readable-stream to newest 2.x and 3.x versions (#76480) --- yarn.lock | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9ab1de22af57c..a37ae19b15a06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24179,7 +24179,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.3.7, readable-stream@~2.3.3: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.3, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -24202,29 +24202,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" - integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.2: +"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== From 34664360312f03c73bf0805115a27c41242e5066 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 2 Sep 2020 08:53:23 -0700 Subject: [PATCH 03/43] [Enterprise Search] Add 404 pages (#76404) * Add new 404 NotFound shared view * Add new telemetry ui_error.not_found metric * Add check for licensed users who should be sent to support.elastic.co --- .../enterprise_search/common/constants.ts | 4 + .../public/applications/app_search/index.tsx | 4 + .../applications/shared/licensing/index.ts | 2 +- .../shared/licensing/license_checks.test.ts | 23 +++- .../shared/licensing/license_checks.ts | 8 +- .../not_found/assets/app_search_logo.tsx | 32 +++++ .../shared/not_found/assets/logo.scss | 21 ++++ .../assets/workplace_search_logo.tsx | 38 ++++++ .../applications/shared/not_found/index.ts | 7 ++ .../shared/not_found/not_found.test.tsx | 65 +++++++++++ .../shared/not_found/not_found.tsx | 110 ++++++++++++++++++ .../applications/workplace_search/index.tsx | 5 + .../collectors/app_search/telemetry.test.ts | 3 + .../server/collectors/app_search/telemetry.ts | 4 + .../workplace_search/telemetry.test.ts | 3 + .../collectors/workplace_search/telemetry.ts | 4 + .../schema/xpack_plugins.json | 6 + 17 files changed, 336 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index c5839df4c603b..05d27d7337a6e 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -24,6 +24,7 @@ export const APP_SEARCH_PLUGIN = { 'Leverage dashboards, analytics, and APIs for advanced application search made simple.', }), URL: '/app/enterprise_search/app_search', + SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/app-search/', }; export const WORKPLACE_SEARCH_PLUGIN = { @@ -36,8 +37,11 @@ export const WORKPLACE_SEARCH_PLUGIN = { 'Search all documents, files, and sources available across your virtual workplace.', }), URL: '/app/enterprise_search/workplace_search', + SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/workplace-search/', }; +export const LICENSED_SUPPORT_URL = 'https://support.elastic.co'; + export const JSON_HEADER = { 'Content-Type': 'application/json' }; // This needs specific casing or Chrome throws a 415 error export const ENGINES_PAGE_SIZE = 10; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 6575e44f509eb..c4a366930d22a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -29,6 +29,7 @@ import { import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; +import { NotFound } from '../shared/not_found'; import { EngineOverview } from './components/engine_overview'; export const AppSearch: React.FC = (props) => { @@ -73,6 +74,9 @@ export const AppSearchConfigured: React.FC = (props) => { + + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts index 9c8c1417d48db..29c11ffa1cef8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/index.ts @@ -5,4 +5,4 @@ */ export { LicenseContext, LicenseProvider, ILicenseContext } from './license_context'; -export { hasPlatinumLicense } from './license_checks'; +export { hasPlatinumLicense, hasGoldLicense } from './license_checks'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts index ad134e7d36b10..40f0f6380c21c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { hasPlatinumLicense } from './license_checks'; +import { hasPlatinumLicense, hasGoldLicense } from './license_checks'; describe('hasPlatinumLicense', () => { it('is true for platinum licenses', () => { @@ -31,3 +31,24 @@ describe('hasPlatinumLicense', () => { expect(hasPlatinumLicense({ isActive: true, type: 'gold' } as any)).toEqual(false); }); }); + +describe('hasGoldLicense', () => { + it('is true for gold+ and trial licenses', () => { + expect(hasGoldLicense({ isActive: true, type: 'gold' } as any)).toEqual(true); + expect(hasGoldLicense({ isActive: true, type: 'platinum' } as any)).toEqual(true); + expect(hasGoldLicense({ isActive: true, type: 'enterprise' } as any)).toEqual(true); + expect(hasGoldLicense({ isActive: true, type: 'trial' } as any)).toEqual(true); + }); + + it('is false if the current license is expired', () => { + expect(hasGoldLicense({ isActive: false, type: 'gold' } as any)).toEqual(false); + expect(hasGoldLicense({ isActive: false, type: 'platinum' } as any)).toEqual(false); + expect(hasGoldLicense({ isActive: false, type: 'enterprise' } as any)).toEqual(false); + expect(hasGoldLicense({ isActive: false, type: 'trial' } as any)).toEqual(false); + }); + + it('is false for licenses below gold', () => { + expect(hasGoldLicense({ isActive: true, type: 'basic' } as any)).toEqual(false); + expect(hasGoldLicense({ isActive: false, type: 'standard' } as any)).toEqual(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts index de4a17ce2bd3c..d13d0909243be 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/licensing/license_checks.ts @@ -7,5 +7,11 @@ import { ILicense } from '../../../../../licensing/public'; export const hasPlatinumLicense = (license?: ILicense) => { - return license?.isActive && ['platinum', 'enterprise', 'trial'].includes(license?.type as string); + const qualifyingLicenses = ['platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type as string); +}; + +export const hasGoldLicense = (license?: ILicense) => { + const qualifyingLicenses = ['gold', 'platinum', 'enterprise', 'trial']; + return license?.isActive && qualifyingLicenses.includes(license?.type as string); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx new file mode 100644 index 0000000000000..6bd0486532576 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx @@ -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 React from 'react'; + +export const AppSearchLogo: React.FC = () => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss new file mode 100644 index 0000000000000..322e6a4182c5c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/logo.scss @@ -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. + */ + +.logo404 { + width: $euiSize * 8; + height: $euiSize * 8; + + fill: $euiColorEmptyShade; + stroke: $euiColorLightShade; + + &__light { + fill: $euiColorLightShade; + } + + &__dark { + fill: $euiColorMediumShade; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx new file mode 100644 index 0000000000000..30bcf9ad2d7fd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export const WorkplaceSearchLogo: React.FC = () => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/index.ts new file mode 100644 index 0000000000000..dcc7762b93214 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/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 { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..ce9071ad7b9d0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.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 '../../__mocks__/shallow_usecontext.mock'; + +import React, { useContext } from 'react'; +import { shallow } from 'enzyme'; + +import { EuiButton as EuiButtonExternal, EuiEmptyPrompt } from '@elastic/eui'; + +import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../common/constants'; +import { AppSearchLogo } from './assets/app_search_logo'; +import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; + +import { NotFound } from './'; + +describe('NotFound', () => { + const basicLicense = { isActive: true, type: 'basic' }; + const goldLicense = { isActive: true, type: 'gold' }; + + beforeEach(() => { + (useContext as jest.Mock).mockImplementation(() => ({ license: basicLicense })); + }); + + it('renders an App Search 404 view', () => { + const wrapper = shallow(); + const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); + + expect(prompt.find('h2').text()).toEqual('404 error'); + expect(prompt.find(EuiButtonExternal).prop('href')).toEqual(APP_SEARCH_PLUGIN.SUPPORT_URL); + + const logo = prompt.find(AppSearchLogo).dive().shallow(); + expect(logo.type()).toEqual('svg'); + }); + + it('renders a Workplace Search 404 view', () => { + const wrapper = shallow(); + const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); + + expect(prompt.find('h2').text()).toEqual('404 error'); + expect(prompt.find(EuiButtonExternal).prop('href')).toEqual( + WORKPLACE_SEARCH_PLUGIN.SUPPORT_URL + ); + + const logo = prompt.find(WorkplaceSearchLogo).dive().shallow(); + expect(logo.type()).toEqual('svg'); + }); + + it('changes the support URL if the user has a gold+ license', () => { + (useContext as jest.Mock).mockImplementation(() => ({ license: goldLicense })); + const wrapper = shallow(); + const prompt = wrapper.find(EuiEmptyPrompt).dive().shallow(); + + expect(prompt.find(EuiButtonExternal).prop('href')).toEqual('https://support.elastic.co'); + }); + + it('does not render anything without a valid product', () => { + const wrapper = shallow(); + + expect(wrapper.isEmptyRender()).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx new file mode 100644 index 0000000000000..bd988854225fb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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, { useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPageContent, + EuiEmptyPrompt, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButton as EuiButtonExternal, +} from '@elastic/eui'; + +import { + APP_SEARCH_PLUGIN, + WORKPLACE_SEARCH_PLUGIN, + LICENSED_SUPPORT_URL, +} from '../../../../common/constants'; + +import { EuiButton } from '../react_router_helpers'; +import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; +import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; +import { LicenseContext, ILicenseContext, hasGoldLicense } from '../licensing'; + +import { AppSearchLogo } from './assets/app_search_logo'; +import { WorkplaceSearchLogo } from './assets/workplace_search_logo'; +import './assets/logo.scss'; + +interface NotFoundProps { + // Expects product plugin constants (@see common/constants.ts) + product: { + ID: string; + SUPPORT_URL: string; + }; +} + +export const NotFound: React.FC = ({ product = {} }) => { + const { license } = useContext(LicenseContext) as ILicenseContext; + const supportUrl = hasGoldLicense(license) ? LICENSED_SUPPORT_URL : product.SUPPORT_URL; + + let Logo; + let SetPageChrome; + let SendTelemetry; + + switch (product.ID) { + case APP_SEARCH_PLUGIN.ID: + Logo = AppSearchLogo; + SetPageChrome = SetAppSearchChrome; + SendTelemetry = SendAppSearchTelemetry; + break; + case WORKPLACE_SEARCH_PLUGIN.ID: + Logo = WorkplaceSearchLogo; + SetPageChrome = SetWorkplaceSearchChrome; + SendTelemetry = SendWorkplaceSearchTelemetry; + break; + default: + return null; + } + + return ( + <> + + + + + } + body={ + <> + +

+ {i18n.translate('xpack.enterpriseSearch.notFound.title', { + defaultMessage: '404 error', + })} +

+
+

+ {i18n.translate('xpack.enterpriseSearch.notFound.description', { + defaultMessage: 'The page you’re looking for was not found.', + })} +

+ + } + actions={ + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action1', { + defaultMessage: 'Back to your dashboard', + })} + + + + + {i18n.translate('xpack.enterpriseSearch.notFound.action2', { + defaultMessage: 'Contact support', + })} + + + + } + /> +
+ + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 23e24e343937d..6a51b49869eaf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -8,6 +8,7 @@ import React, { useContext, useEffect } from 'react'; import { Route, Redirect, Switch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; +import { WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; import { IInitialAppData } from '../../../common/types'; import { KibanaContext, IKibanaContext } from '../index'; import { HttpLogic } from '../shared/http'; @@ -19,6 +20,7 @@ import { SETUP_GUIDE_PATH } from './routes'; import { SetupGuide } from './views/setup_guide'; import { ErrorState } from './views/error_state'; +import { NotFound } from '../shared/not_found'; import { Overview } from './views/overview'; export const WorkplaceSearch: React.FC = (props) => { @@ -53,6 +55,9 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { {/* Will replace with groups component subsequent PR */}
+ + + )} diff --git a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts index 189f8278f1b07..6532e06f7051c 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.test.ts @@ -22,6 +22,7 @@ describe('App Search Telemetry Usage Collector', () => { 'ui_viewed.setup_guide': 10, 'ui_viewed.engines_overview': 20, 'ui_error.cannot_connect': 3, + 'ui_error.not_found': 7, 'ui_clicked.create_first_engine_button': 40, 'ui_clicked.header_launch_button': 50, 'ui_clicked.engine_table_link': 60, @@ -60,6 +61,7 @@ describe('App Search Telemetry Usage Collector', () => { }, ui_error: { cannot_connect: 3, + not_found: 7, }, ui_clicked: { create_first_engine_button: 40, @@ -86,6 +88,7 @@ describe('App Search Telemetry Usage Collector', () => { }, ui_error: { cannot_connect: 0, + not_found: 0, }, ui_clicked: { create_first_engine_button: 0, diff --git a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts index f700088cb67a0..ede776b6e0dce 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/app_search/telemetry.ts @@ -17,6 +17,7 @@ interface ITelemetry { }; ui_error: { cannot_connect: number; + not_found: number; }; ui_clicked: { create_first_engine_button: number; @@ -47,6 +48,7 @@ export const registerTelemetryUsageCollector = ( }, ui_error: { cannot_connect: { type: 'long' }, + not_found: { type: 'long' }, }, ui_clicked: { create_first_engine_button: { type: 'long' }, @@ -77,6 +79,7 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log }, ui_error: { cannot_connect: 0, + not_found: 0, }, ui_clicked: { create_first_engine_button: 0, @@ -97,6 +100,7 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log }, ui_error: { cannot_connect: get(savedObjectAttributes, 'ui_error.cannot_connect', 0), + not_found: get(savedObjectAttributes, 'ui_error.not_found', 0), }, ui_clicked: { create_first_engine_button: get( diff --git a/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.test.ts index 8960d6fa9b67b..f7d73cb232b8d 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.test.ts @@ -22,6 +22,7 @@ describe('Workplace Search Telemetry Usage Collector', () => { 'ui_viewed.setup_guide': 10, 'ui_viewed.overview': 20, 'ui_error.cannot_connect': 3, + 'ui_error.not_found': 7, 'ui_clicked.header_launch_button': 30, 'ui_clicked.org_name_change_button': 40, 'ui_clicked.onboarding_card_button': 50, @@ -61,6 +62,7 @@ describe('Workplace Search Telemetry Usage Collector', () => { }, ui_error: { cannot_connect: 3, + not_found: 7, }, ui_clicked: { header_launch_button: 30, @@ -88,6 +90,7 @@ describe('Workplace Search Telemetry Usage Collector', () => { }, ui_error: { cannot_connect: 0, + not_found: 0, }, ui_clicked: { header_launch_button: 0, diff --git a/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.ts index 892de5cfee35e..cc08febf41f45 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/workplace_search/telemetry.ts @@ -17,6 +17,7 @@ interface ITelemetry { }; ui_error: { cannot_connect: number; + not_found: number; }; ui_clicked: { header_launch_button: number; @@ -48,6 +49,7 @@ export const registerTelemetryUsageCollector = ( }, ui_error: { cannot_connect: { type: 'long' }, + not_found: { type: 'long' }, }, ui_clicked: { header_launch_button: { type: 'long' }, @@ -79,6 +81,7 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log }, ui_error: { cannot_connect: 0, + not_found: 0, }, ui_clicked: { header_launch_button: 0, @@ -100,6 +103,7 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log }, ui_error: { cannot_connect: get(savedObjectAttributes, 'ui_error.cannot_connect', 0), + not_found: get(savedObjectAttributes, 'ui_error.not_found', 0), }, ui_clicked: { header_launch_button: get(savedObjectAttributes, 'ui_clicked.header_launch_button', 0), diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 5280f094e3a5d..2435d8a9aaf04 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -30,6 +30,9 @@ "properties": { "cannot_connect": { "type": "long" + }, + "not_found": { + "type": "long" } } }, @@ -64,6 +67,9 @@ "properties": { "cannot_connect": { "type": "long" + }, + "not_found": { + "type": "long" } } }, From 7b7b013b977ec75a8c379b50ae42bb729c66b384 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 2 Sep 2020 08:59:58 -0700 Subject: [PATCH 04/43] [DOCS] Adds cloud icon to alerting settings (#76516) --- docs/settings/alert-action-settings.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index e02c7f212277e..88858c36643ec 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -37,12 +37,12 @@ You can configure the following settings in the `kibana.yml` file. [cols="2*<"] |=== -| `xpack.actions.whitelistedHosts` +| `xpack.actions.whitelistedHosts` {ess-icon} | A list of hostnames that {kib} is allowed to connect to when built-in actions are triggered. It defaults to `[*]`, allowing any host, but keep in mind the potential for SSRF attacks when hosts are not explicitly whitelisted. An empty list `[]` can be used to block built-in actions from making any external connections. + + Note that hosts associated with built-in actions, such as Slack and PagerDuty, are not automatically whitelisted. If you are not using the default `[*]` setting, you must ensure that the corresponding endpoints are whitelisted as well. -| `xpack.actions.enabledActionTypes` +| `xpack.actions.enabledActionTypes` {ess-icon} | A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, and `.webhook`. An empty list `[]` will disable all action types. + + Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. From 71b9dedfc4eadd97db2b6b0dc038d0f640babf7b Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 2 Sep 2020 12:07:32 -0400 Subject: [PATCH 05/43] [Ingest Manager] Remove success: true as top-level API response (#73223) * Big bang commit removing top-level success property in API response Left in check-permissions and in array of objects returned by delete package configs * Remove success property from mocks * Resolve conflict from upstream changes * Remove success property (after upstream merge) * Remove more 'success'es after merging in upstream * Remove success from some tests * Remove success from OpenAPI spec * Revert prior try/catch. Use res.ok Co-authored-by: Elastic Machine --- .../common/openapi/spec_oas3.json | 94 ++++--------------- .../common/types/rest_spec/agent.ts | 20 ++-- .../common/types/rest_spec/agent_policy.ts | 7 -- .../types/rest_spec/enrollment_api_key.ts | 4 - .../common/types/rest_spec/epm.ts | 6 -- .../common/types/rest_spec/output.ts | 3 - .../common/types/rest_spec/package_policy.ts | 3 - .../common/types/rest_spec/settings.ts | 2 - .../components/agent_policy_copy_provider.tsx | 6 +- .../agent_policy_delete_provider.tsx | 6 +- .../components/settings/index.tsx | 2 +- .../components/create_agent_policy.tsx | 2 +- .../scripts/dev_agent/script.ts | 2 +- .../server/routes/agent/acks_handlers.test.ts | 1 - .../server/routes/agent/acks_handlers.ts | 1 - .../routes/agent/actions_handlers.test.ts | 1 - .../server/routes/agent/actions_handlers.ts | 1 - .../server/routes/agent/handlers.ts | 13 +-- .../server/routes/agent/unenroll_handler.ts | 4 +- .../server/routes/agent_policy/handlers.ts | 8 +- .../routes/enrollment_api_key/handler.ts | 8 +- .../server/routes/epm/handlers.ts | 6 -- .../server/routes/output/handler.ts | 3 - .../server/routes/package_policy/handlers.ts | 6 +- .../server/routes/settings/index.ts | 2 - .../server/services/agent_policy.ts | 1 - .../store/mock_endpoint_result_list.ts | 3 - .../store/policy_list/test_mock_utils.ts | 2 - .../apis/agent_policy/agent_policy.ts | 7 +- .../apis/fleet/agents/acks.ts | 1 - .../apis/fleet/agents/actions.ts | 1 - .../apis/fleet/agents/checkin.ts | 1 - .../apis/fleet/agents/complete_flow.ts | 14 +-- .../apis/fleet/agents/delete.ts | 2 - .../apis/fleet/agents/enroll.ts | 3 +- .../apis/fleet/agents/list.ts | 6 +- .../apis/fleet/agents/unenroll.ts | 10 +- .../apis/fleet/enrollment_api_keys/crud.ts | 7 +- .../apis/package_policy/create.ts | 5 +- .../apis/package_policy/get.ts | 7 +- .../apis/package_policy/update.ts | 5 +- .../apis/artifacts/index.ts | 1 - 42 files changed, 58 insertions(+), 229 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json index 8c9ede1fde9ba..d75a914e080d7 100644 --- a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json +++ b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json @@ -43,12 +43,9 @@ }, "perPage": { "type": "number" - }, - "success": { - "type": "boolean" } }, - "required": ["items", "total", "page", "perPage", "success"] + "required": ["items", "total", "page", "perPage"] }, "examples": { "success": { @@ -71,8 +68,7 @@ ], "total": 1, "page": 1, - "perPage": 50, - "success": true + "perPage": 50 } } } @@ -107,9 +103,6 @@ "properties": { "item": { "$ref": "#/components/schemas/AgentPolicy" - }, - "success": { - "type": "boolean" } } } @@ -159,12 +152,9 @@ "properties": { "item": { "$ref": "#/components/schemas/AgentPolicy" - }, - "success": { - "type": "boolean" } }, - "required": ["item", "success"] + "required": ["item"] }, "examples": { "success": { @@ -707,8 +697,7 @@ "revision": 2, "updated_on": "2020-05-06T17:32:21.905Z", "updated_by": "system" - }, - "success": true + } } } } @@ -733,12 +722,9 @@ "properties": { "item": { "$ref": "#/components/schemas/AgentPolicy" - }, - "success": { - "type": "boolean" } }, - "required": ["item", "success"] + "required": ["item"] }, "examples": { "example-1": { @@ -751,8 +737,7 @@ "updated_on": "Fri Feb 28 2020 16:22:31 GMT-0500 (Eastern Standard Time)", "updated_by": "elastic", "packagePolicies": [] - }, - "success": true + } } } } @@ -810,12 +795,9 @@ "properties": { "item": { "$ref": "#/components/schemas/AgentPolicy" - }, - "success": { - "type": "boolean" } }, - "required": ["item", "success"] + "required": ["item"] } } } @@ -948,12 +930,9 @@ }, "perPage": { "type": "number" - }, - "success": { - "type": "boolean" } }, - "required": ["items", "success"] + "required": ["items"] }, "examples": { "example-1": { @@ -1157,8 +1136,7 @@ ], "total": 6, "page": 1, - "perPage": 20, - "success": true + "perPage": 20 } } } @@ -1251,12 +1229,9 @@ "properties": { "item": { "$ref": "#/components/schemas/PackagePolicy" - }, - "success": { - "type": "boolean" } }, - "required": ["item", "success"] + "required": ["item"] } } } @@ -1415,9 +1390,6 @@ "properties": { "response": { "$ref": "#/components/schemas/PackageInfo" - }, - "success": { - "type": "boolean" } } }, @@ -1709,8 +1681,7 @@ }, "references": [] } - }, - "success": true + } } }, "required-package": { @@ -1899,8 +1870,7 @@ }, "references": [] } - }, - "success": true + } } } } @@ -1950,12 +1920,9 @@ }, "required": ["id", "type"] } - }, - "success": { - "type": "boolean" } }, - "required": ["response", "success"] + "required": ["response"] } } } @@ -1994,12 +1961,9 @@ }, "required": ["id", "type"] } - }, - "success": { - "type": "boolean" } }, - "required": ["response", "success"] + "required": ["response"] } } } @@ -2678,8 +2642,7 @@ "version": "1.0.0", "status": "not_installed" } - ], - "success": true + ] } } } @@ -2743,9 +2706,6 @@ "type": "object" } }, - "success": { - "type": "boolean" - }, "total": { "type": "number" }, @@ -2756,7 +2716,7 @@ "type": "number" } }, - "required": ["list", "success", "total", "page", "perPage"] + "required": ["list", "total", "page", "perPage"] }, "examples": { "example-1": { @@ -2793,7 +2753,6 @@ "status": "online" } ], - "success": true, "total": 1, "page": 1, "perPage": 20 @@ -2836,12 +2795,9 @@ "properties": { "item": { "type": "object" - }, - "success": { - "type": "string" } }, - "required": ["item", "success"] + "required": ["item"] } } } @@ -2916,9 +2872,6 @@ "type": "string", "enum": ["checkin"] }, - "success": { - "type": "string" - }, "actions": { "type": "array", "items": { @@ -2950,7 +2903,6 @@ "success": { "value": { "action": "checkin", - "success": true, "actions": [ { "agent_id": "a6f14bd2-1a2a-481c-9212-9494d064ffdf", @@ -3346,21 +3298,17 @@ "schema": { "type": "object", "properties": { - "success": { - "type": "boolean" - }, "action": { "type": "string", "enum": ["acks"] } }, - "required": ["success", "action"] + "required": ["action"] }, "examples": { "success": { "value": { - "action": "checkin", - "success": true + "action": "checkin" } } } @@ -3417,9 +3365,6 @@ "action": { "type": "string" }, - "success": { - "type": "boolean" - }, "item": { "$ref": "#/components/schemas/Agent" } @@ -3429,7 +3374,6 @@ "success": { "value": { "action": "created", - "success": true, "item": { "id": "8086fb1a-72ca-4a67-8533-09300c1639fa", "active": true, diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts index 4a50938049a7b..cf8d3ab1c908a 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts @@ -28,7 +28,6 @@ export interface GetAgentsResponse { total: number; page: number; perPage: number; - success: boolean; } export interface GetOneAgentRequest { @@ -39,7 +38,6 @@ export interface GetOneAgentRequest { export interface GetOneAgentResponse { item: Agent; - success: boolean; } export interface PostAgentCheckinRequest { @@ -55,7 +53,7 @@ export interface PostAgentCheckinRequest { export interface PostAgentCheckinResponse { action: string; - success: boolean; + actions: AgentAction[]; } @@ -72,7 +70,7 @@ export interface PostAgentEnrollRequest { export interface PostAgentEnrollResponse { action: string; - success: boolean; + item: Agent & { status: AgentStatus }; } @@ -87,7 +85,6 @@ export interface PostAgentAcksRequest { export interface PostAgentAcksResponse { action: string; - success: boolean; } export interface PostNewAgentActionRequest { @@ -100,7 +97,6 @@ export interface PostNewAgentActionRequest { } export interface PostNewAgentActionResponse { - success: boolean; item: AgentAction; } @@ -110,9 +106,8 @@ export interface PostAgentUnenrollRequest { }; } -export interface PostAgentUnenrollResponse { - success: boolean; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PostAgentUnenrollResponse {} export interface PutAgentReassignRequest { params: { @@ -121,9 +116,8 @@ export interface PutAgentReassignRequest { body: { policy_id: string }; } -export interface PutAgentReassignResponse { - success: boolean; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface PutAgentReassignResponse {} export interface GetOneAgentEventsRequest { params: { @@ -141,7 +135,6 @@ export interface GetOneAgentEventsResponse { total: number; page: number; perPage: number; - success: boolean; } export interface DeleteAgentRequest { @@ -166,7 +159,6 @@ export interface GetAgentStatusRequest { } export interface GetAgentStatusResponse { - success: boolean; results: { events: number; total: number; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts index 4683727ed51ac..aa9fbc20fc0b0 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_policy.ts @@ -19,7 +19,6 @@ export interface GetAgentPoliciesResponse { total: number; page: number; perPage: number; - success: boolean; } export interface GetOneAgentPolicyRequest { @@ -30,7 +29,6 @@ export interface GetOneAgentPolicyRequest { export interface GetOneAgentPolicyResponse { item: AgentPolicy; - success: boolean; } export interface CreateAgentPolicyRequest { @@ -39,7 +37,6 @@ export interface CreateAgentPolicyRequest { export interface CreateAgentPolicyResponse { item: AgentPolicy; - success: boolean; } export type UpdateAgentPolicyRequest = GetOneAgentPolicyRequest & { @@ -48,7 +45,6 @@ export type UpdateAgentPolicyRequest = GetOneAgentPolicyRequest & { export interface UpdateAgentPolicyResponse { item: AgentPolicy; - success: boolean; } export interface CopyAgentPolicyRequest { @@ -57,7 +53,6 @@ export interface CopyAgentPolicyRequest { export interface CopyAgentPolicyResponse { item: AgentPolicy; - success: boolean; } export interface DeleteAgentPolicyRequest { @@ -68,7 +63,6 @@ export interface DeleteAgentPolicyRequest { export interface DeleteAgentPolicyResponse { id: string; - success: boolean; } export interface GetFullAgentPolicyRequest { @@ -79,5 +73,4 @@ export interface GetFullAgentPolicyRequest { export interface GetFullAgentPolicyResponse { item: FullAgentPolicy; - success: boolean; } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts index 1929955cac84c..1d1c2f79bf6cb 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/enrollment_api_key.ts @@ -19,7 +19,6 @@ export interface GetEnrollmentAPIKeysResponse { total: number; page: number; perPage: number; - success: boolean; } export interface GetOneEnrollmentAPIKeyRequest { @@ -30,7 +29,6 @@ export interface GetOneEnrollmentAPIKeyRequest { export interface GetOneEnrollmentAPIKeyResponse { item: EnrollmentAPIKey; - success: boolean; } export interface DeleteEnrollmentAPIKeyRequest { @@ -41,7 +39,6 @@ export interface DeleteEnrollmentAPIKeyRequest { export interface DeleteEnrollmentAPIKeyResponse { action: string; - success: boolean; } export interface PostEnrollmentAPIKeyRequest { @@ -55,5 +52,4 @@ export interface PostEnrollmentAPIKeyRequest { export interface PostEnrollmentAPIKeyResponse { action: string; item: EnrollmentAPIKey; - success: boolean; } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts index 1901b8c0c7039..5fb718f91b876 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts @@ -20,7 +20,6 @@ export interface GetCategoriesRequest { export interface GetCategoriesResponse { response: CategorySummaryList; - success: boolean; } export interface GetPackagesRequest { @@ -39,12 +38,10 @@ export interface GetPackagesResponse { > > >; - success: boolean; } export interface GetLimitedPackagesResponse { response: string[]; - success: boolean; } export interface GetFileRequest { @@ -62,7 +59,6 @@ export interface GetInfoRequest { export interface GetInfoResponse { response: PackageInfo; - success: boolean; } export interface InstallPackageRequest { @@ -73,7 +69,6 @@ export interface InstallPackageRequest { export interface InstallPackageResponse { response: AssetReference[]; - success: boolean; } export interface DeletePackageRequest { @@ -84,5 +79,4 @@ export interface DeletePackageRequest { export interface DeletePackageResponse { response: AssetReference[]; - success: boolean; } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts index 4162060363381..87e8a0977e3ba 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts @@ -7,7 +7,6 @@ import { Output } from '../models'; export interface GetOneOutputResponse { item: Output; - success: boolean; } export interface GetOneOutputRequest { @@ -28,7 +27,6 @@ export interface PutOutputRequest { export interface PutOutputResponse { item: Output; - success: boolean; } export interface GetOutputsResponse { @@ -36,5 +34,4 @@ export interface GetOutputsResponse { total: number; page: number; perPage: number; - success: boolean; } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts index e5bba3d6deab3..61669ab876d80 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/package_policy.ts @@ -18,7 +18,6 @@ export interface GetPackagePoliciesResponse { total: number; page: number; perPage: number; - success: boolean; } export interface GetOnePackagePolicyRequest { @@ -29,7 +28,6 @@ export interface GetOnePackagePolicyRequest { export interface GetOnePackagePolicyResponse { item: PackagePolicy; - success: boolean; } export interface CreatePackagePolicyRequest { @@ -38,7 +36,6 @@ export interface CreatePackagePolicyRequest { export interface CreatePackagePolicyResponse { item: PackagePolicy; - success: boolean; } export type UpdatePackagePolicyRequest = GetOnePackagePolicyRequest & { diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts index c02a5e5878ee9..90d623b8a4be9 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts @@ -7,7 +7,6 @@ import { Settings } from '../models'; export interface GetSettingsResponse { item: Settings; - success: boolean; } export interface PutSettingsRequest { @@ -16,5 +15,4 @@ export interface PutSettingsRequest { export interface PutSettingsResponse { item: Settings; - success: boolean; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx index 8a91cabe78d00..fefbe1fa82a0c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_copy_provider.tsx @@ -61,7 +61,7 @@ export const AgentPolicyCopyProvider: React.FunctionComponent = ({ childr try { const { data } = await sendCopyAgentPolicy(agentPolicy!.id, newAgentPolicy!); - if (data?.success) { + if (data) { notifications.toasts.addSuccess( i18n.translate('xpack.ingestManager.copyAgentPolicy.successNotificationTitle', { defaultMessage: 'Agent policy copied', @@ -70,9 +70,7 @@ export const AgentPolicyCopyProvider: React.FunctionComponent = ({ childr if (onSuccessCallback.current) { onSuccessCallback.current(data.item); } - } - - if (!data?.success) { + } else { notifications.toasts.addDanger( i18n.translate('xpack.ingestManager.copyAgentPolicy.failureNotificationTitle', { defaultMessage: "Error copying agent policy '{id}'", diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx index 08367bf2a97bf..15f71689ecdfa 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_delete_provider.tsx @@ -59,7 +59,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ chil agentPolicyId: agentPolicy!, }); - if (data?.success) { + if (data) { notifications.toasts.addSuccess( i18n.translate('xpack.ingestManager.deleteAgentPolicy.successSingleNotificationTitle', { defaultMessage: "Deleted agent policy '{id}'", @@ -69,9 +69,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ chil if (onSuccessCallback.current) { onSuccessCallback.current(agentPolicy!); } - } - - if (!data?.success) { + } else { notifications.toasts.addDanger( i18n.translate('xpack.ingestManager.deleteAgentPolicy.failureSingleNotificationTitle', { defaultMessage: "Error deleting agent policy '{id}'", diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx index 766bdc2e2c193..b8fbb4f1835a6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/details_page/components/settings/index.tsx @@ -82,7 +82,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( namespace, monitoring_enabled, }); - if (data?.success) { + if (data) { notifications.toasts.addSuccess( i18n.translate('xpack.ingestManager.editAgentPolicy.successNotificationTitle', { defaultMessage: "Successfully updated '{name}' settings", diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx index b072990727e3c..e3e2975777e17 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/list_page/components/create_agent_policy.tsx @@ -116,7 +116,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ try { const { data, error } = await createAgentPolicy(); setIsLoading(false); - if (data?.success) { + if (data) { notifications.toasts.addSuccess( i18n.translate( 'xpack.ingestManager.createAgentPolicy.successNotificationTitle', diff --git a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts index c7b8edd0c332e..65375a076e9a4 100644 --- a/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts +++ b/x-pack/plugins/ingest_manager/scripts/dev_agent/script.ts @@ -122,7 +122,7 @@ async function enroll(kibanaURL: string, apiKey: string, log: ToolingLog): Promi }); const obj: PostAgentEnrollResponse = await res.json(); - if (!obj.success) { + if (!res.ok) { log.error(JSON.stringify(obj, null, 2)); throw new Error('unable to enroll'); } diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts index aaed189ae3ddd..33b9dc617075b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts @@ -88,7 +88,6 @@ describe('test acks handlers', () => { await postAgentAcksHandler(({} as unknown) as RequestHandlerContext, mockRequest, mockResponse); expect(mockResponse.ok.mock.calls[0][0]?.body as PostAgentAcksResponse).toEqual({ action: 'acks', - success: true, }); }); }); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts index 0b719d8a67df7..564f5d03e9450 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts @@ -46,7 +46,6 @@ export const postAgentAcksHandlerBuilder = function ( const body: PostAgentAcksResponse = { action: 'acks', - success: true, }; return response.ok({ body }); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts index bcb9a7797f26a..5445a46fbe2b4 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts @@ -97,6 +97,5 @@ describe('test actions handlers', () => { ?.body as unknown) as PostNewAgentActionResponse; expect(expectedAgentActionResponse.item).toEqual(agentAction); - expect(expectedAgentActionResponse.success).toEqual(true); }); }); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts index 81893b1e78338..b81d44c40f8eb 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts @@ -35,7 +35,6 @@ export const postNewAgentActionHandlerBuilder = function ( }); const body: PostNewAgentActionResponse = { - success: true, item: savedAgentAction, }; diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index 3e80a74dc2b11..2bce8daa6637e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -43,7 +43,6 @@ export const getAgentHandler: RequestHandler ({ agent_id: agent.id, type: a.type, @@ -248,7 +243,6 @@ export const postAgentEnrollHandler: RequestHandler< ); const body: PostAgentEnrollResponse = { action: 'created', - success: true, item: { ...agent, status: AgentService.getAgentStatus(agent), @@ -289,7 +283,6 @@ export const getAgentsHandler: RequestHandler< ...agent, status: AgentService.getAgentStatus(agent), })), - success: true, total, page, perPage, @@ -312,9 +305,7 @@ export const putAgentsReassignHandler: RequestHandler< try { await AgentService.reassignAgent(soClient, request.params.agentId, request.body.policy_id); - const body: PutAgentReassignResponse = { - success: true, - }; + const body: PutAgentReassignResponse = {}; return response.ok({ body }); } catch (e) { return response.customError({ @@ -336,7 +327,7 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler< request.query.policyId ); - const body: GetAgentStatusResponse = { results, success: true }; + const body: GetAgentStatusResponse = { results }; return response.ok({ body }); } catch (e) { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts index d1e54fe4fb3a1..5df695d248f5b 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/unenroll_handler.ts @@ -23,9 +23,7 @@ export const postAgentsUnenrollHandler: RequestHandler< await AgentService.unenrollAgent(soClient, request.params.agentId); } - const body: PostAgentUnenrollResponse = { - success: true, - }; + const body: PostAgentUnenrollResponse = {}; return response.ok({ body }); } catch (e) { return response.customError({ diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts index cc178e1038e2e..7eb3df2346106 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_policy/handlers.ts @@ -49,7 +49,6 @@ export const getAgentPoliciesHandler: RequestHandler< total, page, perPage, - success: true, }; await bluebird.map( @@ -82,7 +81,6 @@ export const getOneAgentPolicyHandler: RequestHandler { return { response: epmPackages, - success: true, }; }, @@ -114,7 +113,6 @@ const endpointListApiPathHandlerMocks = ({ return { items: [agentPolicy], total: 10, - success: true, perPage: 10, page: 1, }; @@ -132,7 +130,6 @@ const endpointListApiPathHandlerMocks = ({ page: 1, perPage: 10, total: endpointPackagePolicies?.length, - success: true, }; }, }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts index 5ef01c00dbf16..1093aed0608d5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/test_mock_utils.ts @@ -64,7 +64,6 @@ export const mockPolicyResultList: (options?: { total, page: requestPageIndex, perPage: requestPageSize, - success: true, }; return mock; }; @@ -81,7 +80,6 @@ export const policyListApiPathHandlers = (totalPolicies: number = 1) => { [INGEST_API_EPM_PACKAGES]: (): GetPackagesResponse => { return { response: [generator.generateEpmPackage()], - success: true, }; }, }; diff --git a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts index b55da0798c5f0..410b0fe093002 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts @@ -14,7 +14,7 @@ export default function ({ getService }: FtrProviderContext) { describe('ingest_manager_agent_policies', () => { describe('POST /api/ingest_manager/agent_policies', () => { it('should work with valid values', async () => { - const { body: apiResponse } = await supertest + await supertest .post(`/api/ingest_manager/agent_policies`) .set('kbn-xsrf', 'xxxx') .send({ @@ -22,8 +22,6 @@ export default function ({ getService }: FtrProviderContext) { namespace: 'default', }) .expect(200); - - expect(apiResponse.success).to.be(true); }); it('should return a 400 with an empty namespace', async () => { @@ -61,7 +59,7 @@ export default function ({ getService }: FtrProviderContext) { it('should work with valid values', async () => { const { - body: { success, item }, + body: { item }, } = await supertest .post(`/api/ingest_manager/agent_policies/${TEST_POLICY_ID}/copy`) .set('kbn-xsrf', 'xxxx') @@ -73,7 +71,6 @@ export default function ({ getService }: FtrProviderContext) { // eslint-disable-next-line @typescript-eslint/naming-convention const { id, updated_at, ...newPolicy } = item; - expect(success).to.be(true); expect(newPolicy).to.eql({ name: 'Copied policy', description: 'Test', diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts index c9fa80c88762b..1613ca9d11ee6 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/acks.ts @@ -90,7 +90,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); expect(apiResponse.action).to.be('acks'); - expect(apiResponse.success).to.be(true); const { body: eventResponse } = await supertest .get(`/api/ingest_manager/fleet/agents/agent1/events`) .set('kbn-xsrf', 'xx') diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/actions.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/actions.ts index 8dc4e5c232b80..2b4bb335dfc5c 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/actions.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/actions.ts @@ -33,7 +33,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(apiResponse.success).to.be(true); expect(apiResponse.item.data).to.eql({ data: 'action_data' }); expect(apiResponse.item.sent_at).to.be('2020-03-18T19:45:02.620Z'); }); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/checkin.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/checkin.ts index 79f6cfae175e1..fbfc94a0840b0 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/checkin.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/checkin.ts @@ -102,7 +102,6 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(apiResponse.action).to.be('checkin'); - expect(apiResponse.success).to.be(true); }); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/complete_flow.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/complete_flow.ts index 2d14a7a10e668..1d5b682d71c7a 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/complete_flow.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/complete_flow.ts @@ -62,7 +62,6 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(200); - expect(enrollmentResponse.success).to.eql(true); const agentAccessAPIKey = enrollmentResponse.item.access_api_key; @@ -76,14 +75,13 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(checkinApiResponse.success).to.eql(true); expect(checkinApiResponse.actions).length(1); expect(checkinApiResponse.actions[0].type).be('CONFIG_CHANGE'); const policyChangeAction = checkinApiResponse.actions[0]; const defaultOutputApiKey = policyChangeAction.data.config.outputs.default.api_key; // Ack actions - const { body: ackApiResponse } = await supertestWithoutAuth + await supertestWithoutAuth .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/acks`) .set('Authorization', `ApiKey ${agentAccessAPIKey}`) .set('kbn-xsrf', 'xx') @@ -102,7 +100,6 @@ export default function (providerContext: FtrProviderContext) { ], }) .expect(200); - expect(ackApiResponse.success).to.eql(true); // Second agent checkin const { body: secondCheckinApiResponse } = await supertestWithoutAuth @@ -113,7 +110,6 @@ export default function (providerContext: FtrProviderContext) { events: [], }) .expect(200); - expect(secondCheckinApiResponse.success).to.eql(true); expect(secondCheckinApiResponse.actions).length(0); // Get agent @@ -121,18 +117,16 @@ export default function (providerContext: FtrProviderContext) { .get(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}`) .expect(200); - expect(getAgentApiResponse.success).to.eql(true); expect(getAgentApiResponse.item.packages).to.contain( 'system', "Agent should run the 'system' package" ); // Unenroll agent - const { body: unenrollResponse } = await supertest + await supertest .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/unenroll`) .set('kbn-xsrf', 'xx') .expect(200); - expect(unenrollResponse.success).to.eql(true); // Checkin after unenrollment const { body: checkinAfterUnenrollResponse } = await supertestWithoutAuth @@ -144,13 +138,12 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(checkinAfterUnenrollResponse.success).to.eql(true); expect(checkinAfterUnenrollResponse.actions).length(1); expect(checkinAfterUnenrollResponse.actions[0].type).be('UNENROLL'); const unenrollAction = checkinAfterUnenrollResponse.actions[0]; // ack unenroll actions - const { body: ackUnenrollApiResponse } = await supertestWithoutAuth + await supertestWithoutAuth .post(`/api/ingest_manager/fleet/agents/${enrollmentResponse.item.id}/acks`) .set('Authorization', `ApiKey ${agentAccessAPIKey}`) .set('kbn-xsrf', 'xx') @@ -168,7 +161,6 @@ export default function (providerContext: FtrProviderContext) { ], }) .expect(200); - expect(ackUnenrollApiResponse.success).to.eql(true); // Checkin after unenrollment acknowledged await supertestWithoutAuth diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/delete.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/delete.ts index dc05b7a4dd792..65fbdabe8bd79 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/delete.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/delete.ts @@ -68,7 +68,6 @@ export default function ({ getService }: FtrProviderContext) { .expect(404); expect(apiResponse).not.to.eql({ - success: true, action: 'deleted', }); }); @@ -88,7 +87,6 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xx') .expect(200); expect(apiResponse).to.eql({ - success: true, action: 'deleted', }); }); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts index 93f656ea952d2..eef0d3beb69d0 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts @@ -136,7 +136,6 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(200); - expect(apiResponse.success).to.eql(true); expect(apiResponse.item).to.have.keys('id', 'active', 'access_api_key', 'type', 'policy_id'); }); @@ -158,7 +157,7 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(200); - expect(apiResponse.success).to.eql(true); + const { body: privileges } = await getEsClientForAPIKey( providerContext, apiResponse.item.access_api_key diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/list.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/list.ts index 23563c6f43bbe..1ee00ed720169 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/list.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/list.ts @@ -78,8 +78,7 @@ export default function ({ getService }: FtrProviderContext) { .auth(users.fleet_admin.username, users.fleet_admin.password) .expect(200); - expect(apiResponse).to.have.keys('success', 'page', 'total', 'list'); - expect(apiResponse.success).to.eql(true); + expect(apiResponse).to.have.keys('page', 'total', 'list'); expect(apiResponse.total).to.eql(4); }); it('should return the list of agents when requesting as a user with fleet read permissions', async () => { @@ -87,8 +86,7 @@ export default function ({ getService }: FtrProviderContext) { .get(`/api/ingest_manager/fleet/agents`) .auth(users.fleet_user.username, users.fleet_user.password) .expect(200); - expect(apiResponse).to.have.keys('success', 'page', 'total', 'list'); - expect(apiResponse.success).to.eql(true); + expect(apiResponse).to.have.keys('page', 'total', 'list'); expect(apiResponse.total).to.eql(4); }); it('should not return the list of agents when requesting as a user without fleet permissions', async () => { diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts index d1ff8731183ba..d24e438fa13ea 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/unenroll.ts @@ -65,20 +65,17 @@ export default function (providerContext: FtrProviderContext) { }); it('allow to unenroll using a list of ids', async () => { - const { body } = await supertest + await supertest .post(`/api/ingest_manager/fleet/agents/agent1/unenroll`) .set('kbn-xsrf', 'xxx') .send({ force: true, }) .expect(200); - - expect(body).to.have.keys('success'); - expect(body.success).to.be(true); }); it('should invalidate related API keys', async () => { - const { body } = await supertest + await supertest .post(`/api/ingest_manager/fleet/agents/agent1/unenroll`) .set('kbn-xsrf', 'xxx') .send({ @@ -86,9 +83,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(body).to.have.keys('success'); - expect(body.success).to.be(true); - const { body: { api_keys: accessAPIKeys }, } = await esClient.security.getApiKey({ id: accessAPIKeyId }); diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts index 275462072919b..6c5d552a51eb9 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/enrollment_api_keys/crud.ts @@ -67,13 +67,11 @@ export default function (providerContext: FtrProviderContext) { }); it('should invalide an existing api keys', async () => { - const { body: apiResponse } = await supertest + await supertest .delete(`/api/ingest_manager/fleet/enrollment-api-keys/${keyId}`) .set('kbn-xsrf', 'xxx') .expect(200); - expect(apiResponse.success).to.eql(true); - const { body: { api_keys: apiKeys }, } = await es.security.getApiKey({ id: esApiKeyId }); @@ -113,7 +111,6 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - expect(apiResponse.success).to.eql(true); expect(apiResponse.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id'); }); @@ -125,7 +122,7 @@ export default function (providerContext: FtrProviderContext) { policy_id: 'policy1', }) .expect(200); - expect(apiResponse.success).to.eql(true); + const { body: privileges } = await getEsClientForAPIKey( providerContext, apiResponse.item.api_key diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts b/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts index c88f03de59615..113fbeca494d8 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { warnAndSkipTest } from '../../helpers'; @@ -34,7 +33,7 @@ export default function ({ getService }: FtrProviderContext) { it('should work with valid values', async function () { if (server.enabled) { - const { body: apiResponse } = await supertest + await supertest .post(`/api/ingest_manager/package_policies`) .set('kbn-xsrf', 'xxxx') .send({ @@ -52,8 +51,6 @@ export default function ({ getService }: FtrProviderContext) { }, }) .expect(200); - - expect(apiResponse.success).to.be(true); } else { warnAndSkipTest(this, log); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_policy/get.ts b/x-pack/test/ingest_manager_api_integration/apis/package_policy/get.ts index 756eaa8ea49ba..53a400d2fd9eb 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_policy/get.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_policy/get.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -75,11 +74,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should succeed with a valid id', async function () { - const { body: apiResponse } = await supertest - .get(`/api/ingest_manager/package_policies/${packagePolicyId}`) - .expect(200); - - expect(apiResponse.success).to.be(true); + await supertest.get(`/api/ingest_manager/package_policies/${packagePolicyId}`).expect(200); }); it('should return a 404 with an invalid id', async function () { diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts b/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts index e74d88f3538e4..3c4cb93ee3ff3 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -77,7 +76,7 @@ export default function (providerContext: FtrProviderContext) { }); it('should work with valid values', async function () { - const { body: apiResponse } = await supertest + await supertest .put(`/api/ingest_manager/package_policies/${packagePolicyId}`) .set('kbn-xsrf', 'xxxx') .send({ @@ -95,8 +94,6 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(200); - - expect(apiResponse.success).to.be(true); }); it('should return a 500 if there is another package policy with the same name', async function () { diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts index d5106f5549924..17a4182fe9371 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/artifacts/index.ts @@ -54,7 +54,6 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(200); - expect(enrollmentResponse.success).to.eql(true); agentAccessAPIKey = enrollmentResponse.item.access_api_key; }); From 1d4a56f7056f6627b823bcfaaea7de4e2d19c1bb Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 2 Sep 2020 11:15:01 -0700 Subject: [PATCH 06/43] [esArchiver] stop importing stream helpers from src (#76519) Co-authored-by: spalger --- .../src/lib/streams/concat_stream.test.js | 74 ++++++++++ .../src/lib/streams/concat_stream.ts | 41 ++++++ .../streams/concat_stream_providers.test.js | 66 +++++++++ .../lib/streams/concat_stream_providers.ts | 60 ++++++++ .../src/lib/streams/filter_stream.test.ts | 77 ++++++++++ .../{streams.ts => streams/filter_stream.ts} | 15 +- .../kbn-es-archiver/src/lib/streams/index.ts | 29 ++++ .../lib/streams/intersperse_stream.test.js | 54 +++++++ .../src/lib/streams/intersperse_stream.ts | 61 ++++++++ .../src/lib/streams/list_stream.test.js | 44 ++++++ .../src/lib/streams/list_stream.ts | 41 ++++++ .../src/lib/streams/map_stream.test.js | 61 ++++++++ .../src/lib/streams/map_stream.ts | 36 +++++ .../lib/streams/promise_from_streams.test.js | 136 ++++++++++++++++++ .../src/lib/streams/promise_from_streams.ts | 64 +++++++++ .../src/lib/streams/reduce_stream.test.js | 84 +++++++++++ .../src/lib/streams/reduce_stream.ts | 77 ++++++++++ .../src/lib/streams/replace_stream.test.js | 130 +++++++++++++++++ .../src/lib/streams/replace_stream.ts | 84 +++++++++++ .../src/lib/streams/split_stream.test.js | 71 +++++++++ .../src/lib/streams/split_stream.ts | 71 +++++++++ 21 files changed, 1375 insertions(+), 1 deletion(-) create mode 100644 packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/concat_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts rename packages/kbn-es-archiver/src/lib/{streams.ts => streams/filter_stream.ts} (71%) create mode 100644 packages/kbn-es-archiver/src/lib/streams/index.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/list_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/list_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/map_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/map_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/replace_stream.ts create mode 100644 packages/kbn-es-archiver/src/lib/streams/split_stream.test.js create mode 100644 packages/kbn-es-archiver/src/lib/streams/split_stream.ts diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js new file mode 100644 index 0000000000000..1498334013d1a --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream.test.js @@ -0,0 +1,74 @@ +/* + * 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 { createListStream, createPromiseFromStreams, createConcatStream } from './'; + +describe('concatStream', () => { + test('accepts an initial value', async () => { + const output = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createConcatStream([0]), + ]); + + expect(output).toEqual([0, 1, 2, 3]); + }); + + describe(`combines using the previous value's concat method`, () => { + test('works with strings', async () => { + const output = await createPromiseFromStreams([ + createListStream(['a', 'b', 'c']), + createConcatStream(), + ]); + expect(output).toEqual('abc'); + }); + + test('works with arrays', async () => { + const output = await createPromiseFromStreams([ + createListStream([[1], [2, 3, 4], [10]]), + createConcatStream(), + ]); + expect(output).toEqual([1, 2, 3, 4, 10]); + }); + + test('works with a mixture, starting with array', async () => { + const output = await createPromiseFromStreams([ + createListStream([[], 1, 2, 3, 4, [5, 6, 7]]), + createConcatStream(), + ]); + expect(output).toEqual([1, 2, 3, 4, 5, 6, 7]); + }); + + test('fails when the value does not have a concat method', async () => { + let promise; + try { + promise = createPromiseFromStreams([createListStream([1, '1']), createConcatStream()]); + } catch (err) { + throw new Error('createPromiseFromStreams() should not fail synchronously'); + } + + try { + await promise; + throw new Error('Promise should have rejected'); + } catch (err) { + expect(err).toBeInstanceOf(Error); + expect(err.message).toContain('concat'); + } + }); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream.ts b/packages/kbn-es-archiver/src/lib/streams/concat_stream.ts new file mode 100644 index 0000000000000..03dd894067afc --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream.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 { createReduceStream } from './reduce_stream'; + +/** + * Creates a Transform stream that consumes all provided + * values and concatenates them using each values `concat` + * method. + * + * Concatenate strings: + * createListStream(['f', 'o', 'o']) + * .pipe(createConcatStream()) + * .on('data', console.log) + * // logs "foo" + * + * Concatenate values into an array: + * createListStream([1,2,3]) + * .pipe(createConcatStream([])) + * .on('data', console.log) + * // logs "[1,2,3]" + */ +export function createConcatStream(initial: any) { + return createReduceStream((acc, chunk) => acc.concat(chunk), initial); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js new file mode 100644 index 0000000000000..b742a770b70c8 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.test.js @@ -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 { Readable } from 'stream'; + +import { concatStreamProviders } from './concat_stream_providers'; +import { createListStream } from './list_stream'; +import { createConcatStream } from './concat_stream'; +import { createPromiseFromStreams } from './promise_from_streams'; + +describe('concatStreamProviders() helper', () => { + test('writes the data from an array of stream providers into a destination stream in order', async () => { + const results = await createPromiseFromStreams([ + concatStreamProviders([ + () => createListStream(['foo', 'bar']), + () => createListStream(['baz']), + () => createListStream(['bug']), + ]), + createConcatStream(''), + ]); + + expect(results).toBe('foobarbazbug'); + }); + + test('emits the errors from a sub-stream to the destination', async () => { + const dest = concatStreamProviders([ + () => createListStream(['foo', 'bar']), + () => + new Readable({ + read() { + this.emit('error', new Error('foo')); + }, + }), + ]); + + const errorListener = jest.fn(); + dest.on('error', errorListener); + + await expect(createPromiseFromStreams([dest])).rejects.toThrowErrorMatchingInlineSnapshot( + `"foo"` + ); + expect(errorListener.mock.calls).toMatchInlineSnapshot(` +Array [ + Array [ + [Error: foo], + ], +] +`); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts new file mode 100644 index 0000000000000..4794d76cc7f84 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/concat_stream_providers.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PassThrough, TransformOptions } from 'stream'; + +/** + * Write the data and errors from a list of stream providers + * to a single stream in order. Stream providers are only + * called right before they will be consumed, and only one + * provider will be active at a time. + */ +export function concatStreamProviders( + sourceProviders: Array<() => NodeJS.ReadableStream>, + options: TransformOptions = {} +) { + const destination = new PassThrough(options); + const queue = sourceProviders.slice(); + + (function pipeNext() { + const provider = queue.shift(); + + if (!provider) { + return; + } + + const source = provider(); + const isLast = !queue.length; + + // if there are more sources to pipe, hook + // into the source completion + if (!isLast) { + source.once('end', pipeNext); + } + + source + // proxy errors from the source to the destination + .once('error', (error) => destination.emit('error', error)) + // pipe the source to the destination but only proxy the + // end event if this is the last source + .pipe(destination, { end: isLast }); + })(); + + return destination; +} diff --git a/packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts b/packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts new file mode 100644 index 0000000000000..28b7f2588628e --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/filter_stream.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { + createConcatStream, + createFilterStream, + createListStream, + createPromiseFromStreams, +} from './'; + +describe('createFilterStream()', () => { + test('calls the function with each item in the source stream', async () => { + const filter = jest.fn().mockReturnValue(true); + + await createPromiseFromStreams([createListStream(['a', 'b', 'c']), createFilterStream(filter)]); + + expect(filter).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + "a", + ], + Array [ + "b", + ], + Array [ + "c", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": true, + }, + Object { + "type": "return", + "value": true, + }, + Object { + "type": "return", + "value": true, + }, + ], + } + `); + }); + + test('send the filtered values on the output stream', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createFilterStream((n) => n % 2 === 0), + createConcatStream([]), + ]); + + expect(result).toMatchInlineSnapshot(` + Array [ + 2, + ] + `); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams.ts b/packages/kbn-es-archiver/src/lib/streams/filter_stream.ts similarity index 71% rename from packages/kbn-es-archiver/src/lib/streams.ts rename to packages/kbn-es-archiver/src/lib/streams/filter_stream.ts index a90afbe0c4d25..738b9d5793d06 100644 --- a/packages/kbn-es-archiver/src/lib/streams.ts +++ b/packages/kbn-es-archiver/src/lib/streams/filter_stream.ts @@ -17,4 +17,17 @@ * under the License. */ -export * from '../../../../src/legacy/utils/streams'; +import { Transform } from 'stream'; + +export function createFilterStream(fn: (obj: T) => boolean) { + return new Transform({ + objectMode: true, + async transform(obj, _, done) { + const canPushDownStream = fn(obj); + if (canPushDownStream) { + this.push(obj); + } + done(); + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/index.ts b/packages/kbn-es-archiver/src/lib/streams/index.ts new file mode 100644 index 0000000000000..447d1ed5b1c53 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { concatStreamProviders } from './concat_stream_providers'; +export { createIntersperseStream } from './intersperse_stream'; +export { createSplitStream } from './split_stream'; +export { createListStream } from './list_stream'; +export { createReduceStream } from './reduce_stream'; +export { createPromiseFromStreams } from './promise_from_streams'; +export { createConcatStream } from './concat_stream'; +export { createMapStream } from './map_stream'; +export { createReplaceStream } from './replace_stream'; +export { createFilterStream } from './filter_stream'; diff --git a/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js new file mode 100644 index 0000000000000..e11b36d77106a --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.test.js @@ -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 { + createPromiseFromStreams, + createListStream, + createIntersperseStream, + createConcatStream, +} from './'; + +describe('intersperseStream', () => { + test('places the intersperse value between each provided value', async () => { + expect( + await createPromiseFromStreams([ + createListStream(['to', 'be', 'or', 'not', 'to', 'be']), + createIntersperseStream(' '), + createConcatStream(), + ]) + ).toBe('to be or not to be'); + }); + + test('emits values as soon as possible, does not needlessly buffer', async () => { + const str = createIntersperseStream('y'); + const onData = jest.fn(); + str.on('data', onData); + + str.write('a'); + expect(onData).toHaveBeenCalledTimes(1); + expect(onData.mock.calls[0]).toEqual(['a']); + onData.mockClear(); + + str.write('b'); + expect(onData).toHaveBeenCalledTimes(2); + expect(onData.mock.calls[0]).toEqual(['y']); + expect(onData).toHaveBeenCalledTimes(2); + expect(onData.mock.calls[1]).toEqual(['b']); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.ts new file mode 100644 index 0000000000000..eb2e3d3087d4a --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/intersperse_stream.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 { Transform } from 'stream'; + +/** + * Create a Transform stream that receives values in object mode, + * and intersperses a chunk between each object received. + * + * This is useful for writing lists: + * + * createListStream(['foo', 'bar']) + * .pipe(createIntersperseStream('\n')) + * .pipe(process.stdout) // outputs "foo\nbar" + * + * Combine with a concat stream to get "join" like functionality: + * + * await createPromiseFromStreams([ + * createListStream(['foo', 'bar']), + * createIntersperseStream(' '), + * createConcatStream() + * ]) // produces a single value "foo bar" + */ +export function createIntersperseStream(intersperseChunk: any) { + let first = true; + + return new Transform({ + writableObjectMode: true, + readableObjectMode: true, + transform(chunk, _, callback) { + try { + if (first) { + first = false; + } else { + this.push(intersperseChunk); + } + + this.push(chunk); + callback(undefined); + } catch (err) { + callback(err); + } + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/list_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/list_stream.test.js new file mode 100644 index 0000000000000..12e20696b0510 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/list_stream.test.js @@ -0,0 +1,44 @@ +/* + * 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 { createListStream } from './'; + +describe('listStream', () => { + test('provides the values in the initial list', async () => { + const str = createListStream([1, 2, 3, 4]); + const onData = jest.fn(); + str.on('data', onData); + + await new Promise((resolve) => str.on('end', resolve)); + + expect(onData).toHaveBeenCalledTimes(4); + expect(onData.mock.calls[0]).toEqual([1]); + expect(onData.mock.calls[1]).toEqual([2]); + expect(onData.mock.calls[2]).toEqual([3]); + expect(onData.mock.calls[3]).toEqual([4]); + }); + + test('does not modify the list passed', async () => { + const list = [1, 2, 3, 4]; + const str = createListStream(list); + str.resume(); + await new Promise((resolve) => str.on('end', resolve)); + expect(list).toEqual([1, 2, 3, 4]); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/list_stream.ts b/packages/kbn-es-archiver/src/lib/streams/list_stream.ts new file mode 100644 index 0000000000000..c061b969b3c09 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/list_stream.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 { Readable } from 'stream'; + +/** + * Create a Readable stream that provides the items + * from a list as objects to subscribers + */ +export function createListStream(items: any | any[] = []) { + const queue: any[] = [].concat(items); + + return new Readable({ + objectMode: true, + read(size) { + queue.splice(0, size).forEach((item) => { + this.push(item); + }); + + if (!queue.length) { + this.push(null); + } + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/map_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/map_stream.test.js new file mode 100644 index 0000000000000..d86da178f0c1b --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/map_stream.test.js @@ -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 { delay } from 'bluebird'; + +import { createPromiseFromStreams } from './promise_from_streams'; +import { createListStream } from './list_stream'; +import { createMapStream } from './map_stream'; +import { createConcatStream } from './concat_stream'; + +describe('createMapStream()', () => { + test('calls the function with each item in the source stream', async () => { + const mapper = jest.fn(); + + await createPromiseFromStreams([createListStream(['a', 'b', 'c']), createMapStream(mapper)]); + + expect(mapper).toHaveBeenCalledTimes(3); + expect(mapper).toHaveBeenCalledWith('a', 0); + expect(mapper).toHaveBeenCalledWith('b', 1); + expect(mapper).toHaveBeenCalledWith('c', 2); + }); + + test('send the return value from the mapper on the output stream', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createMapStream((n) => n * 100), + createConcatStream([]), + ]); + + expect(result).toEqual([100, 200, 300]); + }); + + test('supports async mappers', async () => { + const result = await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createMapStream(async (n, i) => { + await delay(n); + return n * i; + }), + createConcatStream([]), + ]); + + expect(result).toEqual([0, 2, 6]); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/map_stream.ts b/packages/kbn-es-archiver/src/lib/streams/map_stream.ts new file mode 100644 index 0000000000000..e88c512a38653 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/map_stream.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Transform } from 'stream'; + +export function createMapStream(fn: (chunk: any, i: number) => T | Promise) { + let i = 0; + + return new Transform({ + objectMode: true, + async transform(value, _, done) { + try { + this.push(await fn(value, i++)); + done(); + } catch (err) { + done(err); + } + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js new file mode 100644 index 0000000000000..e4d9835106f12 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.test.js @@ -0,0 +1,136 @@ +/* + * 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 { Readable, Writable, Duplex, Transform } from 'stream'; + +import { createListStream, createPromiseFromStreams, createReduceStream } from './'; + +describe('promiseFromStreams', () => { + test('pipes together an array of streams', async () => { + const str1 = createListStream([1, 2, 3]); + const str2 = createReduceStream((acc, n) => acc + n, 0); + const sumPromise = new Promise((resolve) => str2.once('data', resolve)); + createPromiseFromStreams([str1, str2]); + await new Promise((resolve) => str2.once('end', resolve)); + expect(await sumPromise).toBe(6); + }); + + describe('last stream is writable', () => { + test('waits for the last stream to finish writing', async () => { + let written = ''; + + await createPromiseFromStreams([ + createListStream(['a']), + new Writable({ + write(chunk, enc, cb) { + setTimeout(() => { + written += chunk; + cb(); + }, 100); + }, + }), + ]); + + expect(written).toBe('a'); + }); + + test('resolves to undefined', async () => { + const result = await createPromiseFromStreams([ + createListStream(['a']), + new Writable({ + write(chunk, enc, cb) { + cb(); + }, + }), + ]); + + expect(result).toBe(undefined); + }); + }); + + describe('last stream is readable', () => { + test(`resolves to it's final value`, async () => { + const result = await createPromiseFromStreams([createListStream(['a', 'b', 'c'])]); + + expect(result).toBe('c'); + }); + }); + + describe('last stream is duplex', () => { + test('waits for writing and resolves to final value', async () => { + let written = ''; + + const duplexReadQueue = []; + const duplexItemsToPush = ['foo', 'bar', null]; + const result = await createPromiseFromStreams([ + createListStream(['a', 'b', 'c']), + new Duplex({ + async read() { + const result = await duplexReadQueue.shift(); + this.push(result); + }, + + write(chunk, enc, cb) { + duplexReadQueue.push( + new Promise((resolve) => { + setTimeout(() => { + written += chunk; + cb(); + resolve(duplexItemsToPush.shift()); + }, 50); + }) + ); + }, + }).setEncoding('utf8'), + ]); + + expect(written).toEqual('abc'); + expect(result).toBe('bar'); + }); + }); + + describe('error handling', () => { + test('read stream gets destroyed when transform stream fails', async () => { + let destroyCalled = false; + const readStream = new Readable({ + read() { + this.push('a'); + this.push('b'); + this.push('c'); + this.push(null); + }, + destroy() { + destroyCalled = true; + }, + }); + const transformStream = new Transform({ + transform(chunk, enc, done) { + done(new Error('Test error')); + }, + }); + try { + await createPromiseFromStreams([readStream, transformStream]); + throw new Error('Should fail'); + } catch (e) { + expect(e.message).toBe('Test error'); + expect(destroyCalled).toBe(true); + } + }); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.ts new file mode 100644 index 0000000000000..fefb18be14780 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/promise_from_streams.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. + */ + +/** + * Take an array of streams, pipe the output + * from each one into the next, listening for + * errors from any of the streams, and then resolve + * the promise once the final stream has finished + * writing/reading. + * + * If the last stream is readable, it's final value + * will be provided as the promise value. + * + * Errors emitted from any stream will cause + * the promise to be rejected with that error. + */ + +import { pipeline, Writable } from 'stream'; +import { promisify } from 'util'; + +const asyncPipeline = promisify(pipeline); + +export async function createPromiseFromStreams(streams: any): Promise { + let finalChunk: any; + const last = streams[streams.length - 1]; + if (typeof last.read !== 'function' && streams.length === 1) { + // For a nicer error than what stream.pipeline throws + throw new Error('A minimum of 2 streams is required when a non-readable stream is given'); + } + if (typeof last.read === 'function') { + // We are pushing a writable stream to capture the last chunk + streams.push( + new Writable({ + // Use object mode even when "last" stream isn't. This allows to + // capture the last chunk as-is. + objectMode: true, + write(chunk, _, done) { + finalChunk = chunk; + done(); + }, + }) + ); + } + + await asyncPipeline(...(streams as [any])); + + return finalChunk; +} diff --git a/packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js new file mode 100644 index 0000000000000..2c073f67f82a8 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.test.js @@ -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 { createReduceStream, createPromiseFromStreams, createListStream } from './'; + +const promiseFromEvent = (name, emitter) => + new Promise((resolve) => emitter.on(name, () => resolve(name))); + +describe('reduceStream', () => { + test('calls the reducer for each item provided', async () => { + const stub = jest.fn(); + await createPromiseFromStreams([ + createListStream([1, 2, 3]), + createReduceStream((val, chunk, enc) => { + stub(val, chunk, enc); + return chunk; + }, 0), + ]); + expect(stub).toHaveBeenCalledTimes(3); + expect(stub.mock.calls[0]).toEqual([0, 1, 'utf8']); + expect(stub.mock.calls[1]).toEqual([1, 2, 'utf8']); + expect(stub.mock.calls[2]).toEqual([2, 3, 'utf8']); + }); + + test('provides the return value of the last iteration of the reducer', async () => { + const result = await createPromiseFromStreams([ + createListStream('abcdefg'.split('')), + createReduceStream((acc) => acc + 1, 0), + ]); + expect(result).toBe(7); + }); + + test('emits an error if an iteration fails', async () => { + const reduce = createReduceStream((acc, i) => expect(i).toBe(1), 0); + const errorEvent = promiseFromEvent('error', reduce); + + reduce.write(1); + reduce.write(2); + reduce.resume(); + await errorEvent; + }); + + test('stops calling the reducer if an iteration fails, emits no data', async () => { + const reducer = jest.fn((acc, i) => { + if (i < 100) return acc + i; + else throw new Error(i); + }); + const reduce$ = createReduceStream(reducer, 0); + + const dataStub = jest.fn(); + const errorStub = jest.fn(); + reduce$.on('data', dataStub); + reduce$.on('error', errorStub); + const endEvent = promiseFromEvent('end', reduce$); + + reduce$.write(1); + reduce$.write(2); + reduce$.write(300); + reduce$.write(400); + reduce$.write(1000); + reduce$.end(); + + await endEvent; + expect(reducer).toHaveBeenCalledTimes(3); + expect(dataStub).toHaveBeenCalledTimes(0); + expect(errorStub).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts new file mode 100644 index 0000000000000..d9458e9a11c33 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/reduce_stream.ts @@ -0,0 +1,77 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Create a transform stream that consumes each chunk it receives + * and passes it to the reducer, which will return the new value + * for the stream. Once all chunks have been received the reduce + * stream provides the result of final call to the reducer to + * subscribers. + */ +export function createReduceStream( + reducer: (acc: any, chunk: any, env: string) => any, + initial: any +) { + let i = -1; + let value = initial; + + // if the reducer throws an error then the value is + // considered invalid and the stream will never provide + // it to subscribers. We will also stop calling the + // reducer for any new data that is provided to us + let failed = false; + + if (typeof reducer !== 'function') { + throw new TypeError('reducer must be a function'); + } + + return new Transform({ + readableObjectMode: true, + writableObjectMode: true, + async transform(chunk, enc, callback) { + try { + if (failed) { + return callback(); + } + + i += 1; + if (i === 0 && initial === undefined) { + value = chunk; + } else { + value = await reducer(value, chunk, enc); + } + + callback(); + } catch (err) { + failed = true; + callback(err); + } + }, + + flush(callback) { + if (!failed) { + this.push(value); + } + + callback(); + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js new file mode 100644 index 0000000000000..01b89f93e5af0 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/replace_stream.test.js @@ -0,0 +1,130 @@ +/* + * 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 { + createReplaceStream, + createConcatStream, + createPromiseFromStreams, + createListStream, + createMapStream, +} from './'; + +async function concatToString(streams) { + return await createPromiseFromStreams([ + ...streams, + createMapStream((buff) => buff.toString('utf8')), + createConcatStream(''), + ]); +} + +describe('replaceStream', () => { + test('produces buffers when it receives buffers', async () => { + const chunks = await createPromiseFromStreams([ + createListStream([Buffer.from('foo'), Buffer.from('bar')]), + createReplaceStream('o', '0'), + createConcatStream([]), + ]); + + chunks.forEach((chunk) => { + expect(chunk).toBeInstanceOf(Buffer); + }); + }); + + test('produces buffers when it receives strings', async () => { + const chunks = await createPromiseFromStreams([ + createListStream(['foo', 'bar']), + createReplaceStream('o', '0'), + createConcatStream([]), + ]); + + chunks.forEach((chunk) => { + expect(chunk).toBeInstanceOf(Buffer); + }); + }); + + test('expects toReplace to be a string', () => { + expect(() => createReplaceStream(Buffer.from('foo'))).toThrowError(/be a string/); + }); + + test('replaces multiple single-char instances in a single chunk', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f00 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces multiple single-char instances in multiple chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f0'), Buffer.from('0 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces single multi-char instances in single chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('f0'), Buffer.from('0 bar')]), + createReplaceStream('0', 'o'), + ]) + ).toBe('foo bar'); + }); + + test('replaces multiple multi-char instances in single chunks', async () => { + expect( + await concatToString([ + createListStream([Buffer.from('foo ba'), Buffer.from('r b'), Buffer.from('az bar')]), + createReplaceStream('bar', '*'), + ]) + ).toBe('foo * baz *'); + }); + + test('replaces multi-char instance that stretches multiple chunks', async () => { + expect( + await concatToString([ + createListStream([ + Buffer.from('foo supe'), + Buffer.from('rcalifra'), + Buffer.from('gilistic'), + Buffer.from('expialid'), + Buffer.from('ocious bar'), + ]), + createReplaceStream('supercalifragilisticexpialidocious', '*'), + ]) + ).toBe('foo * bar'); + }); + + test('ignores missing multi-char instance', async () => { + expect( + await concatToString([ + createListStream([ + Buffer.from('foo supe'), + Buffer.from('rcalifra'), + Buffer.from('gili stic'), + Buffer.from('expialid'), + Buffer.from('ocious bar'), + ]), + createReplaceStream('supercalifragilisticexpialidocious', '*'), + ]) + ).toBe('foo supercalifragili sticexpialidocious bar'); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/replace_stream.ts b/packages/kbn-es-archiver/src/lib/streams/replace_stream.ts new file mode 100644 index 0000000000000..fe2ba1fcdf31c --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/replace_stream.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 { Transform } from 'stream'; + +export function createReplaceStream(toReplace: string, replacement: string) { + if (typeof toReplace !== 'string') { + throw new TypeError('toReplace must be a string'); + } + + let buffer = Buffer.alloc(0); + return new Transform({ + objectMode: false, + async transform(value, _, done) { + try { + buffer = Buffer.concat([buffer, value], buffer.length + value.length); + + while (true) { + // try to find the next instance of `toReplace` in buffer + const index = buffer.indexOf(toReplace); + + // if there is no next instance, break + if (index === -1) { + break; + } + + // flush everything to the left of the next instance + // of `toReplace` + this.push(buffer.slice(0, index)); + + // then flush an instance of `replacement` + this.push(replacement); + + // and finally update the buffer to include everything + // to the right of `toReplace`, dropping to replace from the buffer + buffer = buffer.slice(index + toReplace.length); + } + + // until now we have only flushed data that is to the left + // of a discovered instance of `toReplace`. If `toReplace` is + // never found this would lead to us buffering the entire stream. + // + // Instead, we only keep enough buffer to complete a potentially + // partial instance of `toReplace` + if (buffer.length > toReplace.length) { + // the entire buffer except the last `toReplace.length` bytes + // so that if all but one byte from `toReplace` is in the buffer, + // and the next chunk delivers the necessary byte, the buffer will then + // contain a complete `toReplace` token. + this.push(buffer.slice(0, buffer.length - toReplace.length)); + buffer = buffer.slice(-toReplace.length); + } + + done(); + } catch (err) { + done(err); + } + }, + + flush(callback) { + if (buffer.length) { + this.push(buffer); + } + + callback(); + }, + }); +} diff --git a/packages/kbn-es-archiver/src/lib/streams/split_stream.test.js b/packages/kbn-es-archiver/src/lib/streams/split_stream.test.js new file mode 100644 index 0000000000000..e0736d220ba5c --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/split_stream.test.js @@ -0,0 +1,71 @@ +/* + * 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 { createSplitStream, createConcatStream, createPromiseFromStreams } from './'; + +async function split(stream, input) { + const concat = createConcatStream(); + concat.write([]); + stream.pipe(concat); + const output = createPromiseFromStreams([concat]); + + input.forEach((i) => { + stream.write(i); + }); + stream.end(); + + return await output; +} + +describe('splitStream', () => { + test('splits buffers, produces strings', async () => { + const output = await split(createSplitStream('&'), [Buffer.from('foo&bar')]); + expect(output).toEqual(['foo', 'bar']); + }); + + test('supports mixed input', async () => { + const output = await split(createSplitStream('&'), [Buffer.from('foo&b'), 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('supports buffer split chunks', async () => { + const output = await split(createSplitStream(Buffer.from('&')), ['foo&b', 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('splits provided values by a delimiter', async () => { + const output = await split(createSplitStream('&'), ['foo&b', 'ar']); + expect(output).toEqual(['foo', 'bar']); + }); + + test('handles multi-character delimiters', async () => { + const output = await split(createSplitStream('oo'), ['foo&b', 'ar']); + expect(output).toEqual(['f', '&bar']); + }); + + test('handles delimiters that span multiple chunks', async () => { + const output = await split(createSplitStream('ba'), ['foo&b', 'ar']); + expect(output).toEqual(['foo&', 'r']); + }); + + test('produces an empty chunk if the split char is at the end of the input', async () => { + const output = await split(createSplitStream('&bar'), ['foo&b', 'ar']); + expect(output).toEqual(['foo', '']); + }); +}); diff --git a/packages/kbn-es-archiver/src/lib/streams/split_stream.ts b/packages/kbn-es-archiver/src/lib/streams/split_stream.ts new file mode 100644 index 0000000000000..1c9b59449bd92 --- /dev/null +++ b/packages/kbn-es-archiver/src/lib/streams/split_stream.ts @@ -0,0 +1,71 @@ +/* + * 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 { Transform } from 'stream'; + +/** + * Creates a Transform stream that consumes a stream of Buffers + * and produces a stream of strings (in object mode) by splitting + * the received bytes using the splitChunk. + * + * Ways this is behaves like String#split: + * - instances of splitChunk are removed from the input + * - splitChunk can be on any size + * - if there are no bytes found after the last splitChunk + * a final empty chunk is emitted + * + * Ways this deviates from String#split: + * - splitChunk cannot be a regexp + * - an empty string or Buffer will not produce a stream of individual + * bytes like `string.split('')` would + */ +export function createSplitStream(splitChunk: string) { + let unsplitBuffer = Buffer.alloc(0); + + return new Transform({ + writableObjectMode: false, + readableObjectMode: true, + transform(chunk, _, callback) { + try { + let i; + let toSplit = Buffer.concat([unsplitBuffer, chunk]); + while ((i = toSplit.indexOf(splitChunk)) !== -1) { + const slice = toSplit.slice(0, i); + toSplit = toSplit.slice(i + splitChunk.length); + this.push(slice.toString('utf8')); + } + + unsplitBuffer = toSplit; + callback(undefined); + } catch (err) { + callback(err); + } + }, + + flush(callback) { + try { + this.push(unsplitBuffer.toString('utf8')); + + callback(undefined); + } catch (err) { + callback(err); + } + }, + }); +} From de3ddbc6e1a986229ad2033b24af7bba6b353497 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 2 Sep 2020 14:22:55 -0400 Subject: [PATCH 07/43] [ML] DF Analytics jobs list: improve job details analysis stats view in expanded row (#76196) * move analysis stats to own section in expanded row * fix type errors * move stats to separate tab in expanded row * handle parameters and hyperparameters not defined --- .../analytics_list/expanded_row.tsx | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index 645c6c276a6f9..95204f9ba09fc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils'; import { DataFrameAnalyticsListRow } from './common'; -import { ExpandedRowDetailsPane, SectionConfig } from './expanded_row_details_pane'; +import { ExpandedRowDetailsPane, SectionConfig, SectionItem } from './expanded_row_details_pane'; import { ExpandedRowJsonPane } from './expanded_row_json_pane'; import { ProgressBar } from './progress_bar'; import { @@ -28,6 +28,7 @@ import { getTaskStateBadge } from './use_columns'; import { getDataFrameAnalyticsProgressPhase, isCompletedAnalyticsJob } from './common'; import { isRegressionAnalysis, + getAnalysisType, ANALYSIS_CONFIG_TYPE, REGRESSION_STATS, isRegressionEvaluateResponse, @@ -76,6 +77,7 @@ export const ExpandedRow: FC = ({ item }) => { const resultsField = item.config.dest.results_field; const jobIsCompleted = isCompletedAnalyticsJob(item.stats); const isRegressionJob = isRegressionAnalysis(item.config.analysis); + const analysisType = getAnalysisType(item.config.analysis); const loadData = async () => { setIsLoadingGeneralization(true); @@ -160,25 +162,34 @@ export const ExpandedRow: FC = ({ item }) => { const stateValues: any = { ...item.stats }; + const analysisStatsValues = stateValues.analysis_stats + ? stateValues.analysis_stats[`${analysisType}_stats`] + : undefined; + if (item.config?.description) { stateValues.description = item.config.description; } delete stateValues.progress; - const state: SectionConfig = { - title: i18n.translate('xpack.ml.dataframe.analyticsList.expandedRow.tabs.jobSettings.state', { - defaultMessage: 'State', - }), - items: Object.entries(stateValues).map(([stateKey, stateValue]) => { + const stateItems = Object.entries(stateValues) + .map(([stateKey, stateValue]) => { const title = stateKey.toString(); if (title === 'state') { return { title, description: getTaskStateBadge(getItemDescription(stateValue)), }; + } else if (title !== 'analysis_stats') { + return { title, description: getItemDescription(stateValue) }; } - return { title, description: getItemDescription(stateValue) }; + }) + .filter((stateItem) => stateItem !== undefined); + + const state: SectionConfig = { + title: i18n.translate('xpack.ml.dataframe.analyticsList.expandedRow.tabs.jobSettings.state', { + defaultMessage: 'State', }), + items: stateItems as SectionItem[], position: 'left', }; @@ -204,7 +215,7 @@ export const ExpandedRow: FC = ({ item }) => { }; }), ], - position: 'left', + position: 'right', }; const stats: SectionConfig = { @@ -221,9 +232,39 @@ export const ExpandedRow: FC = ({ item }) => { { title: 'model_memory_limit', description: item.config.model_memory_limit }, { title: 'version', description: item.config.version }, ], - position: 'right', + position: 'left', }; + const analysisStats: SectionConfig | undefined = analysisStatsValues + ? { + title: i18n.translate( + 'xpack.ml.dataframe.analyticsList.expandedRow.tabs.jobSettings.analysisStats', + { + defaultMessage: 'Analysis stats', + } + ), + items: [ + { + title: 'timestamp', + description: formatHumanReadableDateTimeSeconds( + moment(analysisStatsValues.timestamp).unix() * 1000 + ), + }, + { + title: 'timing_stats', + description: getItemDescription(analysisStatsValues.timing_stats), + }, + ...Object.entries( + analysisStatsValues.parameters || analysisStatsValues.hyperparameters || {} + ).map(([stateKey, stateValue]) => { + const title = stateKey.toString(); + return { title, description: getItemDescription(stateValue) }; + }), + ], + position: 'right', + } + : undefined; + if (jobIsCompleted && isRegressionJob) { stats.items.push( { @@ -309,13 +350,30 @@ export const ExpandedRow: FC = ({ item }) => { ); } + const detailsSections: SectionConfig[] = [state, progress]; + const statsSections: SectionConfig[] = [stats]; + + if (analysisStats !== undefined) { + statsSections.push(analysisStats); + } + const tabs = [ { id: 'ml-analytics-job-details', name: i18n.translate('xpack.ml.dataframe.analyticsList.expandedRow.tabs.jobSettingsLabel', { defaultMessage: 'Job details', }), - content: , + content: , + }, + { + id: 'ml-analytics-job-stats', + name: i18n.translate( + 'xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsStatsLabel', + { + defaultMessage: 'Job stats', + } + ), + content: , }, { id: 'ml-analytics-job-json', From b8822da21e48a944ffb5248c800f09c904b91add Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 2 Sep 2020 20:45:52 +0200 Subject: [PATCH 08/43] Bump end-of-stream from v1.4.1 to v1.4.4 (#76479) --- packages/kbn-pm/dist/index.js | 11 +++++++++-- yarn.lock | 9 +-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index eb2d0d2581a34..9a3bb1c687032 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -27651,6 +27651,7 @@ var eos = function(stream, opts, callback) { var rs = stream._readableState; var readable = opts.readable || (opts.readable !== false && stream.readable); var writable = opts.writable || (opts.writable !== false && stream.writable); + var cancelled = false; var onlegacyfinish = function() { if (!stream.writable) onfinish(); @@ -27675,8 +27676,13 @@ var eos = function(stream, opts, callback) { }; var onclose = function() { - if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); - if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); + process.nextTick(onclosenexttick); + }; + + var onclosenexttick = function() { + if (cancelled) return; + if (readable && !(rs && (rs.ended && !rs.destroyed))) return callback.call(stream, new Error('premature close')); + if (writable && !(ws && (ws.ended && !ws.destroyed))) return callback.call(stream, new Error('premature close')); }; var onrequest = function() { @@ -27701,6 +27707,7 @@ var eos = function(stream, opts, callback) { stream.on('close', onclose); return function() { + cancelled = true; stream.removeListener('complete', onfinish); stream.removeListener('abort', onclose); stream.removeListener('request', onrequest); diff --git a/yarn.lock b/yarn.lock index a37ae19b15a06..56530a0ab4fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11392,14 +11392,7 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== - dependencies: - once "^1.4.0" - -end-of-stream@^1.4.1, end-of-stream@^1.4.4: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== From 535eb4069e24d3f725286affedd72f06997330d0 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Wed, 2 Sep 2020 14:53:35 -0400 Subject: [PATCH 09/43] [SECURITY_SOLUTION][ENDPOINT] Trusted app delete api (#76435) * Trusted app delete api --- .../common/endpoint/constants.ts | 1 + .../common/endpoint/schema/trusted_apps.ts | 6 +++ .../endpoint/routes/trusted_apps/handlers.ts | 29 ++++++++++++ .../endpoint/routes/trusted_apps/index.ts | 18 +++++++- .../routes/trusted_apps/trusted_apps.test.ts | 45 ++++++++++++++++++- .../endpoint/routes/trusted_apps/types.ts | 10 +++++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index b72a52f0a0eb7..366bf7a1df1f2 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -14,3 +14,4 @@ export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100; export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps'; export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps'; +export const TRUSTED_APPS_DELETE_API = '/api/endpoint/trusted_apps/{id}'; diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts index 7535b23a10e8a..7c0de84b637c9 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts @@ -6,6 +6,12 @@ import { schema } from '@kbn/config-schema'; +export const DeleteTrustedAppsRequestSchema = { + params: schema.object({ + id: schema.string(), + }), +}; + export const GetTrustedAppsRequestSchema = { query: schema.object({ page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts index 977683ab55495..a3e6f54f3eee8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.ts @@ -13,6 +13,35 @@ import { import { EndpointAppContext } from '../../types'; import { exceptionItemToTrustedAppItem, newTrustedAppItemToExceptionItem } from './utils'; import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; +import { DeleteTrustedAppsRequestParams } from './types'; + +export const getTrustedAppsDeleteRouteHandler = ( + endpointAppContext: EndpointAppContext +): RequestHandler => { + const logger = endpointAppContext.logFactory.get('trusted_apps'); + + return async (context, req, res) => { + const exceptionsListService = endpointAppContext.service.getExceptionsList(); + + try { + const { id } = req.params; + const response = await exceptionsListService.deleteExceptionListItem({ + id, + itemId: undefined, + namespaceType: 'agnostic', + }); + + if (response === null) { + return res.notFound({ body: `trusted app id [${id}] not found` }); + } + + return res.ok(); + } catch (error) { + logger.error(error); + return res.internalError({ body: error }); + } + }; +}; export const getTrustedAppsListRouteHandler = ( endpointAppContext: EndpointAppContext diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts index 1302b10533ccf..5f502555ee153 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/index.ts @@ -6,20 +6,36 @@ import { IRouter } from 'kibana/server'; import { + DeleteTrustedAppsRequestSchema, GetTrustedAppsRequestSchema, PostTrustedAppCreateRequestSchema, } from '../../../../common/endpoint/schema/trusted_apps'; import { TRUSTED_APPS_CREATE_API, + TRUSTED_APPS_DELETE_API, TRUSTED_APPS_LIST_API, } from '../../../../common/endpoint/constants'; -import { getTrustedAppsCreateRouteHandler, getTrustedAppsListRouteHandler } from './handlers'; +import { + getTrustedAppsCreateRouteHandler, + getTrustedAppsDeleteRouteHandler, + getTrustedAppsListRouteHandler, +} from './handlers'; import { EndpointAppContext } from '../../types'; export const registerTrustedAppsRoutes = ( router: IRouter, endpointAppContext: EndpointAppContext ) => { + // DELETE one + router.delete( + { + path: TRUSTED_APPS_DELETE_API, + validate: DeleteTrustedAppsRequestSchema, + options: { authRequired: true }, + }, + getTrustedAppsDeleteRouteHandler(endpointAppContext) + ); + // GET list router.get( { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts index 488c8390411b0..2325036ef40ae 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/trusted_apps.test.ts @@ -9,11 +9,12 @@ import { createMockEndpointAppContext, createMockEndpointAppContextServiceStartContract, } from '../../mocks'; -import { IRouter, RequestHandler } from 'kibana/server'; +import { IRouter, KibanaRequest, RequestHandler } from 'kibana/server'; import { httpServerMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; import { registerTrustedAppsRoutes } from './index'; import { TRUSTED_APPS_CREATE_API, + TRUSTED_APPS_DELETE_API, TRUSTED_APPS_LIST_API, } from '../../../../common/endpoint/constants'; import { @@ -26,6 +27,7 @@ import { EndpointAppContext } from '../../types'; import { ExceptionListClient } from '../../../../../lists/server'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response'; +import { DeleteTrustedAppsRequestParams } from './types'; describe('when invoking endpoint trusted apps route handlers', () => { let routerMock: jest.Mocked; @@ -220,4 +222,45 @@ describe('when invoking endpoint trusted apps route handlers', () => { expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled(); }); }); + + describe('when deleting a trusted app', () => { + let routeHandler: RequestHandler; + let request: KibanaRequest; + + beforeEach(() => { + [, routeHandler] = routerMock.delete.mock.calls.find(([{ path }]) => + path.startsWith(TRUSTED_APPS_DELETE_API) + )!; + + request = httpServerMock.createKibanaRequest({ + path: TRUSTED_APPS_DELETE_API.replace('{id}', '123'), + method: 'delete', + }); + }); + + it('should return 200 on successful delete', async () => { + await routeHandler(context, request, response); + expect(exceptionsListClient.deleteExceptionListItem).toHaveBeenCalledWith({ + id: request.params.id, + itemId: undefined, + namespaceType: 'agnostic', + }); + expect(response.ok).toHaveBeenCalled(); + }); + + it('should return 404 if item does not exist', async () => { + exceptionsListClient.deleteExceptionListItem.mockResolvedValueOnce(null); + await routeHandler(context, request, response); + expect(response.notFound).toHaveBeenCalled(); + }); + + it('should log unexpected error if one occurs', async () => { + exceptionsListClient.deleteExceptionListItem.mockImplementation(() => { + throw new Error('expected error for delete'); + }); + await routeHandler(context, request, response); + expect(response.internalError).toHaveBeenCalled(); + expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts new file mode 100644 index 0000000000000..13c8bcfc20793 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { TypeOf } from '@kbn/config-schema'; +import { DeleteTrustedAppsRequestSchema } from '../../../../common/endpoint/schema/trusted_apps'; + +export type DeleteTrustedAppsRequestParams = TypeOf; From 6f94bf7d2aaac6f329456357b0f4164a6a8cbe30 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Wed, 2 Sep 2020 13:55:02 -0500 Subject: [PATCH 10/43] [Enterprise Search] Migrate shared components used in Workplace Search Groups (#76345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add shared images and export file * Migrate SourceIcon component * Add util to format routes We need a way to format routes using parametrized routes in Workplace Search. We used to the `react-router-named-routes` library, but it is 5 years old has has no typings. I extracted the function we needed into a util. * Update recent_activity to use formatRoute For the 7.9 Kibana MVP, we used a temporary function to generate this route. Aligning this component to use the helper other parts of the app will use after migration. * Add types and constants Various types and constants migrated for use in groups component * Migrate SourceRow component Also adds a mock for contentSources. For now the array only has one item needed for this test, but in future tests, more sources will be added to that array. * Migrate SourcesTable component * Migrate TablePaginationBar component * Migrate UserIcon component * Migrate UserRow component * Refactor format_route Uses Object.entries instead of for…in. This is more modern and allows for 100% test coverage. Also removed `toString()` as `encodeURIComponent()` seems to automatically cast to string * Remove unused import * Use forEach instead of map We’re not returning anything so map is not needed * Minify images * Remove formatRoute in favor of generatePath Unike formatRoute, generatePath adds the trailing slash automatically * Stop renaming methods Co-authored-by: Elastic Machine --- .../__mocks__/content_sources.mock.ts | 36 ++++ .../workplace_search/__mocks__/users.mock.ts | 17 ++ .../components/shared/assets/box.svg | 1 + .../components/shared/assets/confluence.svg | 1 + .../shared/assets/connection_illustration.svg | 1 + .../components/shared/assets/crawler.svg | 1 + .../components/shared/assets/custom.svg | 1 + .../components/shared/assets/drive.svg | 1 + .../components/shared/assets/dropbox.svg | 1 + .../components/shared/assets/github.svg | 1 + .../components/shared/assets/gmail.svg | 1 + .../components/shared/assets/google.svg | 1 + .../components/shared/assets/google_drive.svg | 1 + .../components/shared/assets/index.ts | 53 ++++++ .../components/shared/assets/jira.svg | 1 + .../components/shared/assets/jira_server.svg | 1 + .../shared/assets/loading_small.svg | 1 + .../components/shared/assets/office365.svg | 1 + .../components/shared/assets/one_drive.svg | 1 + .../components/shared/assets/outlook.svg | 1 + .../components/shared/assets/people.svg | 1 + .../components/shared/assets/salesforce.svg | 1 + .../components/shared/assets/service_now.svg | 1 + .../components/shared/assets/share_point.svg | 1 + .../components/shared/assets/slack.svg | 1 + .../components/shared/assets/zendesk.svg | 1 + .../components/shared/source_icon/index.ts | 7 + .../shared/source_icon/source_icon.test.tsx | 27 +++ .../shared/source_icon/source_icon.tsx | 34 ++++ .../components/shared/source_row/index.ts | 7 + .../shared/source_row/source_row.test.tsx | 83 +++++++++ .../shared/source_row/source_row.tsx | 171 ++++++++++++++++++ .../components/shared/sources_table/index.ts | 7 + .../sources_table/sources_table.test.tsx | 35 ++++ .../shared/sources_table/sources_table.tsx | 44 +++++ .../shared/table_pagination_bar/index.ts | 7 + .../table_pagination_bar.test.tsx | 35 ++++ .../table_pagination_bar.tsx | 82 +++++++++ .../components/shared/user_icon/index.ts | 7 + .../shared/user_icon/user_icon.test.tsx | 42 +++++ .../components/shared/user_icon/user_icon.tsx | 19 ++ .../components/shared/user_row/index.ts | 7 + .../shared/user_row/user_row.test.tsx | 29 +++ .../components/shared/user_row/user_row.tsx | 23 +++ .../workplace_search/constants.ts | 19 ++ .../workplace_search/routes.test.tsx | 38 ++++ .../applications/workplace_search/routes.ts | 8 +- .../applications/workplace_search/types.ts | 40 ++++ .../views/overview/recent_activity.tsx | 4 +- 49 files changed, 901 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/users.mock.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts new file mode 100644 index 0000000000000..7789d0caba345 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.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. + */ + +export const contentSources = [ + { + id: '123', + serviceType: 'custom', + searchable: true, + supportedByLicense: true, + status: 'foo', + statusMessage: 'bar', + name: 'source', + documentCount: '123', + isFederatedSource: false, + errorReason: 0, + allowsReauth: true, + boost: 1, + }, + { + id: '123', + serviceType: 'jira', + searchable: true, + supportedByLicense: true, + status: 'synced', + statusMessage: 'all green', + name: 'Jira', + documentCount: '34234', + isFederatedSource: false, + errorReason: 0, + allowsReauth: true, + boost: 0.5, + }, +]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/users.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/users.mock.ts new file mode 100644 index 0000000000000..0294803042ada --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/users.mock.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. + */ + +export const users = [ + { + id: '1z1z', + name: 'John Does', + pictureUrl: 'http://google.cats', + color: '#ededed', + initials: 'JD', + email: 'jd@elastic.co', + groupIds: [], + }, +]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg new file mode 100644 index 0000000000000..1e7324d9581a7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg new file mode 100644 index 0000000000000..23eff13915401 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg new file mode 100644 index 0000000000000..c1c3c3b3ee9ee --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg new file mode 100644 index 0000000000000..d241989f1aff1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg new file mode 100644 index 0000000000000..f8f6415dea22b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg new file mode 100644 index 0000000000000..40b65df3a1ce3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg new file mode 100644 index 0000000000000..d16f293fde6dc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg new file mode 100644 index 0000000000000..c4b4176560d5b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg new file mode 100644 index 0000000000000..ee824f730aca6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg new file mode 100644 index 0000000000000..22630f533dcbf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg new file mode 100644 index 0000000000000..59469d814e35f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts new file mode 100644 index 0000000000000..5f93694da09b8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 box from './box.svg'; +import confluence from './confluence.svg'; +import crawler from './crawler.svg'; +import custom from './custom.svg'; +import drive from './drive.svg'; +import dropbox from './dropbox.svg'; +import github from './github.svg'; +import gmail from './gmail.svg'; +import google from './google.svg'; +import googleDrive from './google_drive.svg'; +import jira from './jira.svg'; +import jiraServer from './jira_server.svg'; +import loadingSmall from './loading_small.svg'; +import office365 from './office365.svg'; +import oneDrive from './one_drive.svg'; +import outlook from './outlook.svg'; +import people from './people.svg'; +import salesforce from './salesforce.svg'; +import serviceNow from './service_now.svg'; +import sharePoint from './share_point.svg'; +import slack from './slack.svg'; +import zendesk from './zendesk.svg'; + +export const images = { + box, + confluence, + crawler, + custom, + drive, + dropbox, + github, + gmail, + googleDrive, + google, + jira, + jiraServer, + loadingSmall, + office365, + oneDrive, + outlook, + people, + salesforce, + serviceNow, + sharePoint, + slack, + zendesk, +} as { [key: string]: string }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg new file mode 100644 index 0000000000000..224bb822a581c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg new file mode 100644 index 0000000000000..71750fb6e25a0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg new file mode 100644 index 0000000000000..159408ea02938 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg new file mode 100644 index 0000000000000..fdce5d02da3cd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg new file mode 100644 index 0000000000000..1856e5e3ce1af --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg new file mode 100644 index 0000000000000..2680bc99cc367 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg new file mode 100644 index 0000000000000..4500c494c23b7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg new file mode 100644 index 0000000000000..510c438a28195 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg new file mode 100644 index 0000000000000..2d0c09db4e1c3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg new file mode 100644 index 0000000000000..8724be9da88cf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg new file mode 100644 index 0000000000000..14dbd0289da84 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg new file mode 100644 index 0000000000000..f7bc1fda0c9ac --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/index.ts new file mode 100644 index 0000000000000..da0d11cc1fdc5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/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 { SourceIcon } from './source_icon'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx new file mode 100644 index 0000000000000..dd37ba9b6d859 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../__mocks__/shallow_usecontext.mock'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { SourceIcon } from './'; + +describe('SourceIcon', () => { + it('renders unwrapped icon', () => { + const wrapper = shallow(); + + expect(wrapper.find('img')).toHaveLength(1); + expect(wrapper.find('.user-group-source')).toHaveLength(0); + }); + + it('renders wrapped icon', () => { + const wrapper = shallow(); + + expect(wrapper.find('.user-group-source')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx new file mode 100644 index 0000000000000..ddbe327a40a30 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import _camelCase from 'lodash/camelCase'; + +import { images } from '../assets'; + +interface ISourceIconProps { + serviceType: string; + name: string; + className?: string; + wrapped?: boolean; +} + +export const SourceIcon: React.FC = ({ + name, + serviceType, + className, + wrapped, +}) => { + const icon = {name}; + return wrapped ? ( +
+ {icon} +
+ ) : ( + <>{icon} + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/index.ts new file mode 100644 index 0000000000000..f7a99b59837a5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/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 { SourceRow, ISourceRow } from './source_row'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx new file mode 100644 index 0000000000000..2bde3b70f82b5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 '../../../../__mocks__/shallow_usecontext.mock'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiTableRow, EuiSwitch, EuiIcon } from '@elastic/eui'; +import { contentSources } from '../../../__mocks__/content_sources.mock'; + +import { SourceIcon } from '../source_icon'; + +import { SourceRow } from './'; + +const onToggle = jest.fn(); + +describe('SourceRow', () => { + it('renders with no "Fix" link', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTableRow)).toHaveLength(1); + expect(wrapper.contains('Fix')).toBeFalsy(); + expect(wrapper.find(SourceIcon).prop('serviceType')).toEqual('custom'); + }); + + it('calls handler on click', () => { + const wrapper = shallow(); + wrapper.find(EuiSwitch).simulate('change', { target: { checked: true } }); + + expect(onToggle).toHaveBeenCalled(); + }); + + it('renders "Fix" link', () => { + const source = { + ...contentSources[0], + status: 'error', + errorReason: 1, + }; + const wrapper = shallow(); + + expect(wrapper.contains('Fix')).toBeTruthy(); + }); + + it('renders loading icon when indexing', () => { + const source = { + ...contentSources[0], + status: 'indexing', + }; + const wrapper = shallow(); + + expect(wrapper.find(SourceIcon).prop('serviceType')).toEqual('loadingSmall'); + }); + + it('renders warning dot when more config needed', () => { + const source = { + ...contentSources[0], + status: 'need-more-config', + }; + const wrapper = shallow(); + + expect(wrapper.find(EuiIcon).prop('color')).toEqual('warning'); + }); + + it('renders remote tooltip when source is federated', () => { + const source = { + ...contentSources[0], + isFederatedSource: true, + }; + const wrapper = shallow(); + + expect(wrapper.find('.source-row__document-count').contains('Remote')).toBeTruthy(); + }); + + it('renders details link', () => { + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="SourceDetailsLink"]')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx new file mode 100644 index 0000000000000..17ca8e58a80fa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.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 React from 'react'; + +import classNames from 'classnames'; +import _kebabCase from 'lodash/kebabCase'; +import { Link } from 'react-router-dom'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSwitch, + EuiSwitchEvent, + EuiTableRow, + EuiTableRowCell, + EuiText, + EuiToolTip, +} from '@elastic/eui'; + +import { SOURCE_STATUSES as statuses } from '../../../constants'; +import { IContentSourceDetails } from '../../../types'; +import { ADD_SOURCE_PATH, SOURCE_DETAILS_PATH, getContentSourcePath } from '../../../routes'; + +import { SourceIcon } from '../source_icon'; + +const CREDENTIALS_INVALID_ERROR_REASON = 1; + +export interface ISourceRow { + showDetails?: boolean; + isOrganization?: boolean; + onSearchableToggle?(sourceId: string, isSearchable: boolean): void; +} + +interface ISourceRowProps extends ISourceRow { + source: IContentSourceDetails; +} + +export const SourceRow: React.FC = ({ + source: { + id, + serviceType, + searchable, + supportedByLicense, + status, + statusMessage, + name, + documentCount, + isFederatedSource, + errorReason, + allowsReauth, + }, + onSearchableToggle, + isOrganization, + showDetails, +}) => { + const isIndexing = status === statuses.INDEXING; + const hasError = status === statuses.ERROR || status === statuses.DISCONNECTED; + const showFix = + isOrganization && hasError && allowsReauth && errorReason === CREDENTIALS_INVALID_ERROR_REASON; + + const rowClass = classNames( + 'source-row', + { 'content-section--disabled': !searchable }, + { 'source-row source-row--error': hasError } + ); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const imageClass = classNames('source-row__icon', { 'source-row__icon--loading': isIndexing }); + + const fixLink = ( + + Fix + + ); + + const remoteTooltip = ( + <> + Remote + + + + + ); + + return ( + + + + + + + + {name} + + + + + + {status === 'need-more-config' && ( + + + + )} + + + {statusMessage} + + + + + + {isFederatedSource ? remoteTooltip : parseInt(documentCount, 10).toLocaleString('en-US')} + + {onSearchableToggle && ( + + onSearchableToggle(id, e.target.checked)} + disabled={!supportedByLicense} + compressed + label="Source Searchable Toggle" + showLabel={false} + data-test-subj="SourceSearchableToggle" + /> + + )} + + + {showFix && {fixLink}} + + {showDetails && ( + + Details + + )} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/index.ts new file mode 100644 index 0000000000000..bb5f490ed472b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/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 { SourcesTable } from './sources_table'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.test.tsx new file mode 100644 index 0000000000000..dfac8525471b4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.test.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 { shallow } from 'enzyme'; + +import { EuiTable } from '@elastic/eui'; +import { TableHeader } from '../../../../shared/table_header/table_header'; +import { contentSources } from '../../../__mocks__/content_sources.mock'; + +import { SourceRow } from '../source_row'; + +import { SourcesTable } from './'; + +const onToggle = jest.fn(); + +describe('SourcesTable', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTable)).toHaveLength(1); + expect(wrapper.find(SourceRow)).toHaveLength(2); + }); + + it('renders "Searchable" header item when toggle fn present', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(TableHeader).prop('headerItems')).toContain('Searchable'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx new file mode 100644 index 0000000000000..184aadc1dad84 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/sources_table/sources_table.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiTable, EuiTableBody } from '@elastic/eui'; + +import { TableHeader } from '../../../../shared/table_header/table_header'; +import { SourceRow, ISourceRow } from '../source_row'; +import { IContentSourceDetails } from '../../../types'; + +interface ISourcesTableProps extends ISourceRow { + sources: IContentSourceDetails[]; +} + +export const SourcesTable: React.FC = ({ + sources, + showDetails, + isOrganization, + onSearchableToggle, +}) => { + const headerItems = ['Source', 'Status', 'Documents']; + if (onSearchableToggle) headerItems.push('Searchable'); + + return ( + + + + {sources.map((source) => ( + + ))} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/index.ts new file mode 100644 index 0000000000000..1598ee760c059 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/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 { TablePaginationBar } from './table_pagination_bar'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.test.tsx new file mode 100644 index 0000000000000..14d4dfecd0094 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.test.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 { shallow } from 'enzyme'; + +import { EuiFlexGroup, EuiTablePagination } from '@elastic/eui'; + +import { TablePaginationBar } from './'; + +const onChange = jest.fn(); + +describe('TablePaginationBar', () => { + it('renders', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiTablePagination)).toHaveLength(1); + expect(wrapper.find(EuiFlexGroup)).toHaveLength(2); + expect(wrapper.find(EuiTablePagination).prop('itemsPerPage')).toEqual(10); + expect(wrapper.find(EuiTablePagination).prop('pageCount')).toEqual(1); + }); + + it('passes max page count when showAllPages false', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(EuiTablePagination).prop('pageCount')).toEqual(100); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.tsx new file mode 100644 index 0000000000000..04f5b9a477058 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/table_pagination_bar/table_pagination_bar.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { EuiFlexGroup, EuiFlexItem, EuiTablePagination } from '@elastic/eui'; + +interface ITablePaginationBarProps { + itemLabel?: string; + itemsPerPage?: number; + totalPages: number; + totalItems: number; + activePage?: number; + hidePerPageOptions?: boolean; + hideLabelCount?: boolean; + clearFiltersLink?: React.ReactElement; + onChangePage(nextPage: number): void; +} + +const MAX_PAGES = 100; + +export const TablePaginationBar: React.FC = ({ + itemLabel = 'Items', + itemsPerPage = 10, + totalPages, + totalItems, + activePage = 1, + hidePerPageOptions = true, + hideLabelCount = false, + onChangePage, + clearFiltersLink, +}) => { + // EUI component starts page at 0. API starts at 1. + const currentPage = activePage - 1; + const showAllPages = totalPages < MAX_PAGES; + + const pageRangeText = () => { + const rangeEnd = activePage === totalPages ? totalItems : itemsPerPage * activePage; + const rangeStart = currentPage * itemsPerPage + 1; + const rangeEl = ( + + {rangeStart}-{rangeEnd} + + ); + const totalEl = {totalItems.toLocaleString()}; + + return ( + + +
+ Showing {rangeEl} + {showAllPages && ( + <> + {' '} + of {totalEl} {itemLabel} + + )} +
+
+ {clearFiltersLink} +
+ ); + }; + + return ( + + {!hideLabelCount && {pageRangeText()}} + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/index.ts new file mode 100644 index 0000000000000..e9ee92bed68ca --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/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 { UserIcon } from './user_icon'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.test.tsx new file mode 100644 index 0000000000000..f8c1d00047825 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.test.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { shallow } from 'enzyme'; + +import { users } from '../../../__mocks__/users.mock'; + +import { UserIcon } from './user_icon'; + +describe('SourcesTable', () => { + it('renders with picture', () => { + const wrapper = shallow(); + + expect(wrapper.find('.avatar')).toHaveLength(1); + expect(wrapper.find('.avatar__image')).toHaveLength(1); + }); + + it('renders without picture', () => { + const user = { + ...users[0], + pictureUrl: null, + }; + const wrapper = shallow(); + + expect(wrapper.find('.avatar')).toHaveLength(1); + expect(wrapper.find('.avatar__text')).toHaveLength(1); + }); + + it('renders fallback "alt" value when name not present', () => { + const user = { + ...users[0], + name: null, + }; + const wrapper = shallow(); + + expect(wrapper.find('img').prop('alt')).toEqual(user.email); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.tsx new file mode 100644 index 0000000000000..28ea303d14eaf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_icon/user_icon.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { IUser } from '../../../types'; + +export const UserIcon: React.FC = ({ name, pictureUrl, color, initials, email }) => ( +
+ {pictureUrl ? ( + {name + ) : ( + {initials} + )} +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/index.ts new file mode 100644 index 0000000000000..cc4650d70dad0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/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 { UserRow } from './user_row'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.test.tsx new file mode 100644 index 0000000000000..20d2732e8c82a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiTableRow } from '@elastic/eui'; + +import { users } from '../../../__mocks__/users.mock'; + +import { UserRow } from './'; + +describe('SourcesTable', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTableRow)).toHaveLength(1); + expect(wrapper.find('span')).toHaveLength(0); + }); + + it('renders with email visible', () => { + const wrapper = shallow(); + + expect(wrapper.find('span')).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.tsx new file mode 100644 index 0000000000000..45866c10a5b21 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/user_row/user_row.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { EuiTableRow, EuiTableRowCell } from '@elastic/eui'; + +import { IUser } from '../../../types'; + +interface IUserRowProps { + user: IUser; + showEmail?: boolean; +} + +export const UserRow: React.FC = ({ user: { name, email }, showEmail }) => ( + + {name} + {showEmail && {email}} + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts new file mode 100644 index 0000000000000..9f313a6995ad5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const MAX_TABLE_ROW_ICONS = 3; + +export const SOURCE_STATUSES = { + INDEXING: 'indexing', + SYNCED: 'synced', + SYNCING: 'syncing', + AWAITING_USER_ACTION: 'awaiting_user_action', + ERROR: 'error', + DISCONNECTED: 'disconnected', + ALWAYS_SYNCED: 'always_synced', +}; + +export const CUSTOM_SERVICE_TYPE = 'custom'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx new file mode 100644 index 0000000000000..d03c0abb441b9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { shallow } from 'enzyme'; + +import { EuiLink } from '@elastic/eui'; + +import { + getContentSourcePath, + SOURCES_PATH, + ORG_SOURCES_PATH, + SOURCE_DETAILS_PATH, +} from './routes'; + +const TestComponent = ({ id, isOrg }: { id: string; isOrg?: boolean }) => { + const href = getContentSourcePath(SOURCE_DETAILS_PATH, id, !!isOrg); + return test; +}; + +describe('getContentSourcePath', () => { + it('should format org route', () => { + const wrapper = shallow(); + const path = wrapper.find(EuiLink).prop('href'); + + expect(path).toEqual(`${ORG_SOURCES_PATH}/123`); + }); + + it('should format user route', () => { + const wrapper = shallow(); + const path = wrapper.find(EuiLink).prop('href'); + + expect(path).toEqual(`${SOURCES_PATH}/123`); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 993a1a378e738..e833dde4c1b72 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { generatePath } from 'react-router-dom'; + import { CURRENT_MAJOR_VERSION } from '../../../common/version'; export const SETUP_GUIDE_PATH = '/setup_guide'; @@ -107,4 +109,8 @@ export const EDIT_SLACK_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/slack/edit`; export const EDIT_ZENDESK_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/zendesk/edit`; export const EDIT_CUSTOM_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/custom/edit`; -export const getSourcePath = (sourceId: string): string => `${ORG_SOURCES_PATH}/${sourceId}`; +export const getContentSourcePath = ( + path: string, + sourceId: string, + isOrganization: boolean +): string => generatePath(isOrganization ? ORG_PATH + path : path, { sourceId }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index a8348a6f69a39..3866da738cbb6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -7,3 +7,43 @@ export * from '../../../common/types/workplace_search'; export type TSpacerSize = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; + +export interface IGroup { + id: string; + name: string; + createdAt: string; + updatedAt: string; + contentSources: IContentSource[]; + users: IUser[]; + usersCount: number; + color?: string; +} + +export interface IUser { + id: string; + name: string | null; + initials: string; + pictureUrl: string | null; + color: string; + email: string; + role?: string; + groupIds: string[]; +} + +export interface IContentSource { + id: string; + serviceType: string; + name: string; +} + +export interface IContentSourceDetails extends IContentSource { + status: string; + statusMessage: string; + documentCount: string; + isFederatedSource: boolean; + searchable: boolean; + supportedByLicense: boolean; + errorReason: number; + allowsReauth: boolean; + boost: number; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx index 3c476be8d10e6..441f45a947a49 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx @@ -15,7 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ContentSection } from '../../components/shared/content_section'; import { sendTelemetry } from '../../../shared/telemetry'; import { KibanaContext, IKibanaContext } from '../../../index'; -import { getSourcePath } from '../../routes'; +import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes'; import { OverviewLogic } from './overview_logic'; @@ -107,7 +107,7 @@ export const RecentActivityItem: React.FC = ({ const linkProps = { onClick, target: '_blank', - href: getWorkplaceSearchUrl(getSourcePath(sourceId)), + href: getWorkplaceSearchUrl(getContentSourcePath(SOURCE_DETAILS_PATH, sourceId, true)), external: true, color: status === 'error' ? 'danger' : 'primary', 'data-test-subj': 'viewSourceDetailsLink', From d9dc47ef369ff91c5e569ebbc534969f78c950be Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 2 Sep 2020 12:17:23 -0700 Subject: [PATCH 11/43] skip flaky suite (#76551) --- .../reporting_api_integration/reporting_and_security/spaces.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts index 0145ca2a18092..6a68bd530cf63 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts @@ -27,7 +27,8 @@ export default function ({ getService }: FtrProviderContext) { ); }; - describe('Exports from Non-default Space', () => { + // FLAKY: https://github.com/elastic/kibana/issues/76551 + describe.skip('Exports from Non-default Space', () => { before(async () => { await esArchiver.load('reporting/ecommerce'); await esArchiver.load('reporting/ecommerce_kibana_spaces'); // dashboard in non default space From aac84240b237ec33aa72d986137f9decc5f64c38 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 2 Sep 2020 14:52:10 -0500 Subject: [PATCH 12/43] [ML] Extend MlUrlGenerator to support other ML pages (#75696) Co-authored-by: Dima Arnautov --- .../ml/common/constants/ml_url_generator.ts | 38 +++ .../ml/common/types/ml_url_generator.ts | 187 +++++++++++++ .../application/contexts/kibana/index.ts | 1 + .../contexts/kibana/use_create_url.ts | 20 ++ .../contexts/kibana/use_navigate_to_path.ts | 25 +- .../back_to_list_panel/back_to_list_panel.tsx | 13 +- .../view_results_panel/view_results_panel.tsx | 19 +- .../explorer/explorer_dashboard_service.ts | 19 +- .../ml/public/application/routing/router.tsx | 1 - .../application/routing/routes/explorer.tsx | 3 +- .../anomaly_detection_urls_generator.ts | 163 +++++++++++ .../ml/public/ml_url_generator/common.ts | 55 ++++ .../data_frame_analytics_urls_generator.ts | 70 +++++ .../data_visualizer_urls_generator.ts | 29 ++ .../ml/public/ml_url_generator/index.ts | 6 + .../ml_url_generator/ml_url_generator.test.ts | 262 ++++++++++++++++++ .../ml_url_generator/ml_url_generator.ts | 91 ++++++ x-pack/plugins/ml/public/plugin.ts | 2 +- .../open_in_anomaly_explorer_action.tsx | 30 +- .../plugins/ml/public/url_generator.test.ts | 34 --- x-pack/plugins/ml/public/url_generator.ts | 118 -------- .../lib/sample_data_sets/sample_data_sets.ts | 4 +- 22 files changed, 982 insertions(+), 208 deletions(-) create mode 100644 x-pack/plugins/ml/common/constants/ml_url_generator.ts create mode 100644 x-pack/plugins/ml/common/types/ml_url_generator.ts create mode 100644 x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/common.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/index.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts create mode 100644 x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts delete mode 100644 x-pack/plugins/ml/public/url_generator.test.ts delete mode 100644 x-pack/plugins/ml/public/url_generator.ts diff --git a/x-pack/plugins/ml/common/constants/ml_url_generator.ts b/x-pack/plugins/ml/common/constants/ml_url_generator.ts new file mode 100644 index 0000000000000..44f33aa329e7a --- /dev/null +++ b/x-pack/plugins/ml/common/constants/ml_url_generator.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR'; + +export const ML_PAGES = { + ANOMALY_DETECTION_JOBS_MANAGE: 'jobs', + ANOMALY_EXPLORER: 'explorer', + SINGLE_METRIC_VIEWER: 'timeseriesexplorer', + DATA_FRAME_ANALYTICS_JOBS_MANAGE: 'data_frame_analytics', + DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration', + /** + * Page: Data Visualizer + */ + DATA_VISUALIZER: 'datavisualizer', + /** + * Page: Data Visualizer + * Open data visualizer by selecting a Kibana index pattern or saved search + */ + DATA_VISUALIZER_INDEX_SELECT: 'datavisualizer_index_select', + /** + * Page: Data Visualizer + * Open data visualizer by importing data from a log file + */ + DATA_VISUALIZER_FILE: 'filedatavisualizer', + /** + * Page: Data Visualizer + * Open index data visualizer viewer page + */ + DATA_VISUALIZER_INDEX_VIEWER: 'jobs/new_job/datavisualizer', + ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: `jobs/new_job/step/job_type`, + SETTINGS: 'settings', + CALENDARS_MANAGE: 'settings/calendars_list', + FILTER_LISTS_MANAGE: 'settings/filter_lists', +} as const; diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts new file mode 100644 index 0000000000000..234be8b6faf90 --- /dev/null +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { RefreshInterval, TimeRange } from '../../../../../src/plugins/data/common/query'; +import { JobId } from '../../../reporting/common/types'; +import { ML_PAGES } from '../constants/ml_url_generator'; + +type OptionalPageState = object | undefined; + +export type MLPageState = PageState extends OptionalPageState + ? { page: PageType; pageState?: PageState } + : PageState extends object + ? { page: PageType; pageState: PageState } + : { page: PageType }; + +export const ANALYSIS_CONFIG_TYPE = { + OUTLIER_DETECTION: 'outlier_detection', + REGRESSION: 'regression', + CLASSIFICATION: 'classification', +} as const; + +type DataFrameAnalyticsType = typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE]; + +export interface MlCommonGlobalState { + time?: TimeRange; +} +export interface MlCommonAppState { + [key: string]: any; +} + +export interface MlIndexBasedSearchState { + index?: string; + savedSearchId?: string; +} + +export interface MlGenericUrlPageState extends MlIndexBasedSearchState { + globalState?: MlCommonGlobalState; + appState?: MlCommonAppState; + [key: string]: any; +} + +export interface MlGenericUrlState { + page: + | typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER + | typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE; + pageState: MlGenericUrlPageState; +} + +export interface AnomalyDetectionQueryState { + jobId?: JobId; + groupIds?: string[]; +} + +export type AnomalyDetectionUrlState = MLPageState< + typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + AnomalyDetectionQueryState | undefined +>; +export interface ExplorerAppState { + mlExplorerSwimlane: { + selectedType?: string; + selectedLanes?: string[]; + selectedTimes?: number[]; + showTopFieldValues?: boolean; + viewByFieldName?: string; + viewByPerPage?: number; + viewByFromPage?: number; + }; + mlExplorerFilter: { + influencersFilterQuery?: unknown; + filterActive?: boolean; + filteredFields?: string[]; + queryString?: string; + }; + query?: any; +} +export interface ExplorerGlobalState { + ml: { jobIds: JobId[] }; + time?: TimeRange; + refreshInterval?: RefreshInterval; +} + +export interface ExplorerUrlPageState { + /** + * Job IDs + */ + jobIds: JobId[]; + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval; + /** + * Optionally set the query. + */ + query?: any; + /** + * Optional state for the swim lane + */ + mlExplorerSwimlane?: ExplorerAppState['mlExplorerSwimlane']; + mlExplorerFilter?: ExplorerAppState['mlExplorerFilter']; +} + +export type ExplorerUrlState = MLPageState; + +export interface TimeSeriesExplorerGlobalState { + ml: { + jobIds: JobId[]; + }; + time?: TimeRange; + refreshInterval?: RefreshInterval; +} + +export interface TimeSeriesExplorerAppState { + zoom?: { + from?: string; + to?: string; + }; + mlTimeSeriesExplorer?: { + detectorIndex?: number; + entities?: Record; + }; + query?: any; +} + +export interface TimeSeriesExplorerPageState + extends Pick, + Pick { + jobIds: JobId[]; + timeRange?: TimeRange; + detectorIndex?: number; + entities?: Record; +} + +export type TimeSeriesExplorerUrlState = MLPageState< + typeof ML_PAGES.SINGLE_METRIC_VIEWER, + TimeSeriesExplorerPageState +>; + +export interface DataFrameAnalyticsQueryState { + jobId?: JobId | JobId[]; + groupIds?: string[]; +} + +export type DataFrameAnalyticsUrlState = MLPageState< + typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + DataFrameAnalyticsQueryState | undefined +>; + +export interface DataVisualizerUrlState { + page: + | typeof ML_PAGES.DATA_VISUALIZER + | typeof ML_PAGES.DATA_VISUALIZER_FILE + | typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT; +} + +export interface DataFrameAnalyticsExplorationQueryState { + ml: { + jobId: JobId; + analysisType: DataFrameAnalyticsType; + }; +} + +export type DataFrameAnalyticsExplorationUrlState = MLPageState< + typeof ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION, + { + jobId: JobId; + analysisType: DataFrameAnalyticsType; + } +>; + +/** + * Union type of ML URL state based on page + */ +export type MlUrlGeneratorState = + | AnomalyDetectionUrlState + | ExplorerUrlState + | TimeSeriesExplorerUrlState + | DataFrameAnalyticsUrlState + | DataFrameAnalyticsExplorationUrlState + | DataVisualizerUrlState + | MlGenericUrlState; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/index.ts b/x-pack/plugins/ml/public/application/contexts/kibana/index.ts index 8a43ae12deb21..44979a1b9c60a 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/index.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/index.ts @@ -9,3 +9,4 @@ export { useNavigateToPath, NavigateToPath } from './use_navigate_to_path'; export { useUiSettings } from './use_ui_settings_context'; export { useTimefilter } from './use_timefilter'; export { useNotifications } from './use_notifications_context'; +export { useMlUrlGenerator } from './use_create_url'; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts new file mode 100644 index 0000000000000..48385ad3ae6a8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.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 { useMlKibana } from './kibana_context'; +import { ML_APP_URL_GENERATOR } from '../../../../common/constants/ml_url_generator'; + +export const useMlUrlGenerator = () => { + const { + services: { + share: { + urlGenerators: { getUrlGenerator }, + }, + }, + } = useMlKibana(); + + return getUrlGenerator(ML_APP_URL_GENERATOR); +}; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/use_navigate_to_path.ts b/x-pack/plugins/ml/public/application/contexts/kibana/use_navigate_to_path.ts index f2db970bf5057..96d41be03a142 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/use_navigate_to_path.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/use_navigate_to_path.ts @@ -21,14 +21,19 @@ export const useNavigateToPath = () => { const location = useLocation(); - return useMemo( - () => (path: string | undefined, preserveSearch = false) => { - navigateToUrl( - getUrlForApp(PLUGIN_ID, { - path: `${path}${preserveSearch === true ? location.search : ''}`, - }) - ); - }, - [location] - ); + return useMemo(() => { + return (path: string | undefined, preserveSearch = false) => { + if (path === undefined) return; + const modifiedPath = `${path}${preserveSearch === true ? location.search : ''}`; + /** + * Handle urls generated by MlUrlGenerator where '/app/ml' is automatically prepended + */ + const url = modifiedPath.includes('/app/ml') + ? modifiedPath + : getUrlForApp(PLUGIN_ID, { + path: modifiedPath, + }); + navigateToUrl(url); + }; + }, [location]); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx index babb557105270..0560c3150a424 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/back_to_list_panel/back_to_list_panel.tsx @@ -7,13 +7,20 @@ import React, { FC, Fragment } from 'react'; import { EuiCard, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useNavigateToPath } from '../../../../../contexts/kibana'; +import { useMlKibana, useMlUrlGenerator } from '../../../../../contexts/kibana'; +import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator'; export const BackToListPanel: FC = () => { - const navigateToPath = useNavigateToPath(); + const urlGenerator = useMlUrlGenerator(); + const { + services: { + application: { navigateToUrl }, + }, + } = useMlKibana(); const redirectToAnalyticsManagementPage = async () => { - await navigateToPath('/data_frame_analytics'); + const url = await urlGenerator.createUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE }); + await navigateToUrl(url); }; return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx index 23a16ba84ef92..b832bfb2549c6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/view_results_panel/view_results_panel.tsx @@ -7,20 +7,27 @@ import React, { FC, Fragment } from 'react'; import { EuiCard, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useNavigateToPath } from '../../../../../contexts/kibana'; -import { getResultsUrl } from '../../../analytics_management/components/analytics_list/common'; +import { useMlUrlGenerator } from '../../../../../contexts/kibana'; import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; - +import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator'; +import { useNavigateToPath } from '../../../../../contexts/kibana'; interface Props { jobId: string; analysisType: ANALYSIS_CONFIG_TYPE; } export const ViewResultsPanel: FC = ({ jobId, analysisType }) => { + const urlGenerator = useMlUrlGenerator(); const navigateToPath = useNavigateToPath(); - const redirectToAnalyticsManagementPage = async () => { - const path = getResultsUrl(jobId, analysisType); + const redirectToAnalyticsExplorationPage = async () => { + const path = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION, + pageState: { + jobId, + analysisType, + }, + }); await navigateToPath(path); }; @@ -38,7 +45,7 @@ export const ViewResultsPanel: FC = ({ jobId, analysisType }) => { defaultMessage: 'View results for the analytics job.', } )} - onClick={redirectToAnalyticsManagementPage} + onClick={redirectToAnalyticsExplorationPage} data-test-subj="analyticsWizardViewResultsCard" /> diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 4d697bcda1a06..e0ed2ea6cf5e0 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -21,6 +21,7 @@ import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_ import { EXPLORER_ACTION } from './explorer_constants'; import { AppStateSelectedCells, TimeRangeBounds } from './explorer_utils'; import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers'; +import { ExplorerAppState } from '../../../common/types/ml_url_generator'; export const ALLOW_CELL_RANGE_SELECTION = true; @@ -49,24 +50,6 @@ const explorerState$: Observable = explorerFilteredAction$.pipe( shareReplay(1) ); -export interface ExplorerAppState { - mlExplorerSwimlane: { - selectedType?: string; - selectedLanes?: string[]; - selectedTimes?: number[]; - showTopFieldValues?: boolean; - viewByFieldName?: string; - viewByPerPage?: number; - viewByFromPage?: number; - }; - mlExplorerFilter: { - influencersFilterQuery?: unknown; - filterActive?: boolean; - filteredFields?: string[]; - queryString?: string; - }; -} - const explorerAppState$: Observable = explorerState$.pipe( map( (state: ExplorerState): ExplorerAppState => { diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index 56c9a19723fba..22a17c4ea089a 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -75,7 +75,6 @@ const MlRoutes: FC<{ pageDeps: PageDependencies; }> = ({ pageDeps }) => { const navigateToPath = useNavigateToPath(); - return ( <> {Object.entries(routes).map(([name, routeFactory]) => { diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 62d7e82a214b5..2f2fc77283ef7 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -22,7 +22,8 @@ import { useSelectedCells } from '../../explorer/hooks/use_selected_cells'; import { mlJobService } from '../../services/job_service'; import { ml } from '../../services/ml_api_service'; import { useExplorerData } from '../../explorer/actions'; -import { ExplorerAppState, explorerService } from '../../explorer/explorer_dashboard_service'; +import { ExplorerAppState } from '../../../../common/types/ml_url_generator'; +import { explorerService } from '../../explorer/explorer_dashboard_service'; import { getDateFormatTz } from '../../explorer/explorer_utils'; import { useJobSelection } from '../../components/job_selector/use_job_selection'; import { useShowCharts } from '../../components/controls/checkbox_showcharts'; diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts new file mode 100644 index 0000000000000..c4aebb108e7b9 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 isEmpty from 'lodash/isEmpty'; +import { + AnomalyDetectionQueryState, + AnomalyDetectionUrlState, + ExplorerAppState, + ExplorerGlobalState, + ExplorerUrlState, + MlGenericUrlState, + TimeSeriesExplorerAppState, + TimeSeriesExplorerGlobalState, + TimeSeriesExplorerUrlState, +} from '../../common/types/ml_url_generator'; +import { ML_PAGES } from '../../common/constants/ml_url_generator'; +import { createIndexBasedMlUrl } from './common'; +import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; +/** + * Creates URL to the Anomaly Detection Job management page + */ +export function createAnomalyDetectionJobManagementUrl( + appBasePath: string, + params: AnomalyDetectionUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`; + if (!params || isEmpty(params)) { + return url; + } + const { jobId, groupIds } = params; + const queryState: AnomalyDetectionQueryState = { + jobId, + groupIds, + }; + + url = setStateToKbnUrl( + 'mlManagement', + queryState, + { useHash: false, storeInHashQuery: false }, + url + ); + return url; +} + +export function createAnomalyDetectionCreateJobSelectType( + appBasePath: string, + pageState: MlGenericUrlState['pageState'] +): string { + return createIndexBasedMlUrl( + appBasePath, + ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE, + pageState + ); +} + +/** + * Creates URL to the Anomaly Explorer page + */ +export function createExplorerUrl( + appBasePath: string, + params: ExplorerUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.ANOMALY_EXPLORER}`; + + if (!params) { + return url; + } + const { + refreshInterval, + timeRange, + jobIds, + query, + mlExplorerSwimlane = {}, + mlExplorerFilter = {}, + } = params; + const appState: Partial = { + mlExplorerSwimlane, + mlExplorerFilter, + }; + if (query) appState.query = query; + + if (jobIds) { + const queryState: Partial = { + ml: { + jobIds, + }, + }; + + if (timeRange) queryState.time = timeRange; + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + url = setStateToKbnUrl>( + '_g', + queryState, + { useHash: false, storeInHashQuery: false }, + url + ); + url = setStateToKbnUrl>( + '_a', + appState, + { useHash: false, storeInHashQuery: false }, + url + ); + } + + return url; +} + +/** + * Creates URL to the SingleMetricViewer page + */ +export function createSingleMetricViewerUrl( + appBasePath: string, + params: TimeSeriesExplorerUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.SINGLE_METRIC_VIEWER}`; + if (!params) { + return url; + } + const { timeRange, jobIds, refreshInterval, zoom, query, detectorIndex, entities } = params; + + const queryState: TimeSeriesExplorerGlobalState = { + ml: { + jobIds, + }, + refreshInterval, + time: timeRange, + }; + + const appState: Partial = {}; + const mlTimeSeriesExplorer: Partial = {}; + + if (detectorIndex !== undefined) { + mlTimeSeriesExplorer.detectorIndex = detectorIndex; + } + if (entities !== undefined) { + mlTimeSeriesExplorer.entities = entities; + } + appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer; + + if (zoom) appState.zoom = zoom; + if (query) + appState.query = { + query_string: query, + }; + url = setStateToKbnUrl( + '_g', + queryState, + { useHash: false, storeInHashQuery: false }, + url + ); + url = setStateToKbnUrl( + '_a', + appState, + { useHash: false, storeInHashQuery: false }, + url + ); + + return url; +} diff --git a/x-pack/plugins/ml/public/ml_url_generator/common.ts b/x-pack/plugins/ml/public/ml_url_generator/common.ts new file mode 100644 index 0000000000000..57cfc52045282 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/common.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 isEmpty from 'lodash/isEmpty'; +import { MlGenericUrlState } from '../../common/types/ml_url_generator'; +import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; + +export function extractParams(urlState: UrlState) { + // page should be guaranteed to exist here but is unknown + // @ts-ignore + const { page, ...params } = urlState; + return { page, params }; +} + +/** + * Creates generic index based search ML url + * e.g. `jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a` + */ +export function createIndexBasedMlUrl( + appBasePath: string, + page: MlGenericUrlState['page'], + pageState: MlGenericUrlState['pageState'] +): string { + const { globalState, appState, index, savedSearchId, ...restParams } = pageState; + let url = `${appBasePath}/${page}`; + + if (index !== undefined && savedSearchId === undefined) { + url = `${url}?index=${index}`; + } + if (index === undefined && savedSearchId !== undefined) { + url = `${url}?savedSearchId=${savedSearchId}`; + } + + if (!isEmpty(restParams)) { + Object.keys(restParams).forEach((key) => { + url = setStateToKbnUrl( + key, + restParams[key], + { useHash: false, storeInHashQuery: false }, + url + ); + }); + } + + if (globalState) { + url = setStateToKbnUrl('_g', globalState, { useHash: false, storeInHashQuery: false }, url); + } + if (appState) { + url = setStateToKbnUrl('_a', appState, { useHash: false, storeInHashQuery: false }, url); + } + return url; +} diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts new file mode 100644 index 0000000000000..8cf10a2acb64f --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.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. + */ + +/** + * Creates URL to the DataFrameAnalytics page + */ +import { + DataFrameAnalyticsExplorationQueryState, + DataFrameAnalyticsExplorationUrlState, + DataFrameAnalyticsQueryState, + DataFrameAnalyticsUrlState, +} from '../../common/types/ml_url_generator'; +import { ML_PAGES } from '../../common/constants/ml_url_generator'; +import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; + +export function createDataFrameAnalyticsJobManagementUrl( + appBasePath: string, + mlUrlGeneratorState: DataFrameAnalyticsUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`; + + if (mlUrlGeneratorState) { + const { jobId, groupIds } = mlUrlGeneratorState; + const queryState: Partial = { + jobId, + groupIds, + }; + + url = setStateToKbnUrl>( + 'mlManagement', + queryState, + { useHash: false, storeInHashQuery: false }, + url + ); + } + + return url; +} + +/** + * Creates URL to the DataFrameAnalytics Exploration page + */ +export function createDataFrameAnalyticsExplorationUrl( + appBasePath: string, + mlUrlGeneratorState: DataFrameAnalyticsExplorationUrlState['pageState'] +): string { + let url = `${appBasePath}/${ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION}`; + + if (mlUrlGeneratorState) { + const { jobId, analysisType } = mlUrlGeneratorState; + const queryState: DataFrameAnalyticsExplorationQueryState = { + ml: { + jobId, + analysisType, + }, + }; + + url = setStateToKbnUrl( + '_g', + queryState, + { useHash: false, storeInHashQuery: false }, + url + ); + } + + return url; +} diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts new file mode 100644 index 0000000000000..24693df5025d9 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/data_visualizer_urls_generator.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Creates URL to the Data Visualizer page + */ +import { DataVisualizerUrlState, MlGenericUrlState } from '../../common/types/ml_url_generator'; +import { createIndexBasedMlUrl } from './common'; +import { ML_PAGES } from '../../common/constants/ml_url_generator'; + +export function createDataVisualizerUrl( + appBasePath: string, + { page }: DataVisualizerUrlState +): string { + return `${appBasePath}/${page}`; +} + +/** + * Creates URL to the Index Data Visualizer + */ +export function createIndexDataVisualizerUrl( + appBasePath: string, + pageState: MlGenericUrlState['pageState'] +): string { + return createIndexBasedMlUrl(appBasePath, ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, pageState); +} diff --git a/x-pack/plugins/ml/public/ml_url_generator/index.ts b/x-pack/plugins/ml/public/ml_url_generator/index.ts new file mode 100644 index 0000000000000..1579b187278c4 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { MlUrlGenerator, registerUrlGenerator } from './ml_url_generator'; diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts new file mode 100644 index 0000000000000..55bc6d3668de7 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { MlUrlGenerator } from './ml_url_generator'; +import { ML_PAGES } from '../../common/constants/ml_url_generator'; +import { ANALYSIS_CONFIG_TYPE } from '../../common/types/ml_url_generator'; + +describe('MlUrlGenerator', () => { + const urlGenerator = new MlUrlGenerator({ + appBasePath: '/app/ml', + useHash: false, + }); + + describe('AnomalyDetection', () => { + describe('Job Management Page', () => { + it('should generate valid URL for the Anomaly Detection job management page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + }); + expect(url).toBe('/app/ml/jobs'); + }); + + it('should generate valid URL for the Anomaly Detection job management page for job', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + pageState: { + jobId: 'fq_single_1', + }, + }); + expect(url).toBe('/app/ml/jobs?mlManagement=(jobId:fq_single_1)'); + }); + + it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + pageState: { + groupIds: ['farequote', 'categorization'], + }, + }); + expect(url).toBe('/app/ml/jobs?mlManagement=(groupIds:!(farequote,categorization))'); + }); + + it('should generate valid URL for the page for selecting the type of anomaly detection job to create', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE, + pageState: { + index: `3da93760-e0af-11ea-9ad3-3bcfc330e42a`, + globalState: { + time: { + from: 'now-30m', + to: 'now', + }, + }, + }, + }); + expect(url).toBe( + '/app/ml/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))' + ); + }); + }); + + describe('Anomaly Explorer Page', () => { + it('should generate valid URL for the Anomaly Explorer page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: ['fq_single_1'], + mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 }, + refreshInterval: { + pause: false, + value: 0, + }, + timeRange: { + from: '2019-02-07T00:00:00.000Z', + to: '2020-08-13T17:15:00.000Z', + mode: 'absolute', + }, + query: { + analyze_wildcard: true, + query: '*', + }, + }, + }); + expect(url).toBe( + "/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*'))" + ); + }); + it('should generate valid URL for the Anomaly Explorer page for multiple jobIds', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: ['fq_single_1', 'logs_categorization_1'], + timeRange: { + from: '2019-02-07T00:00:00.000Z', + to: '2020-08-13T17:15:00.000Z', + mode: 'absolute', + }, + }, + }); + expect(url).toBe( + "/app/ml/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:())" + ); + }); + }); + + describe('Single Metric Viewer Page', () => { + it('should generate valid URL for the Single Metric Viewer page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.SINGLE_METRIC_VIEWER, + pageState: { + jobIds: ['logs_categorization_1'], + refreshInterval: { + pause: false, + value: 0, + }, + timeRange: { + from: '2020-07-12T00:39:02.912Z', + to: '2020-07-22T15:52:18.613Z', + mode: 'absolute', + }, + query: { + analyze_wildcard: true, + query: '*', + }, + }, + }); + expect(url).toBe( + "/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*')))" + ); + }); + + it('should generate valid URL for the Single Metric Viewer page with extra settings', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.SINGLE_METRIC_VIEWER, + pageState: { + jobIds: ['logs_categorization_1'], + detectorIndex: 0, + entities: { mlcategory: '2' }, + refreshInterval: { + pause: false, + value: 0, + }, + timeRange: { + from: '2020-07-12T00:39:02.912Z', + to: '2020-07-22T15:52:18.613Z', + mode: 'absolute', + }, + zoom: { + from: '2020-07-20T23:58:29.367Z', + to: '2020-07-21T11:00:13.173Z', + }, + query: { + analyze_wildcard: true, + query: '*', + }, + }, + }); + expect(url).toBe( + "/app/ml/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2')),query:(query_string:(analyze_wildcard:!t,query:'*')),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z'))" + ); + }); + }); + }); + + describe('DataFrameAnalytics', () => { + describe('JobManagement Page', () => { + it('should generate valid URL for the Data Frame Analytics job management page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + }); + expect(url).toBe('/app/ml/data_frame_analytics'); + }); + it('should generate valid URL for the Data Frame Analytics job management page with jobId', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + pageState: { + jobId: 'grid_regression_1', + }, + }); + expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(jobId:grid_regression_1)'); + }); + + it('should generate valid URL for the Data Frame Analytics job management page with groupIds', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + pageState: { + groupIds: ['group_1', 'group_2'], + }, + }); + expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(groupIds:!(group_1,group_2))'); + }); + }); + + describe('ExplorationPage', () => { + it('should generate valid URL for the Data Frame Analytics exploration page for job', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION, + pageState: { + jobId: 'grid_regression_1', + analysisType: ANALYSIS_CONFIG_TYPE.REGRESSION, + }, + }); + expect(url).toBe( + '/app/ml/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))' + ); + }); + }); + }); + + describe('DataVisualizer', () => { + it('should generate valid URL for the Data Visualizer page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_VISUALIZER, + }); + expect(url).toBe('/app/ml/datavisualizer'); + }); + + it('should generate valid URL for the File Data Visualizer import page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_VISUALIZER_FILE, + }); + expect(url).toBe('/app/ml/filedatavisualizer'); + }); + + it('should generate valid URL for the Index Data Visualizer select index pattern or saved search page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT, + }); + expect(url).toBe('/app/ml/datavisualizer_index_select'); + }); + + it('should generate valid URL for the Index Data Visualizer Viewer page', async () => { + const url = await urlGenerator.createUrl({ + page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER, + pageState: { + index: '3da93760-e0af-11ea-9ad3-3bcfc330e42a', + globalState: { + time: { + from: 'now-30m', + to: 'now', + }, + }, + }, + }); + expect(url).toBe( + '/app/ml/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))' + ); + }); + }); + + it('should throw an error in case the page is not provided', async () => { + expect.assertions(1); + + // @ts-ignore + await urlGenerator.createUrl({ jobIds: ['test-job'] }).catch((e) => { + expect(e.message).toEqual('Page type is not provided or unknown'); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts new file mode 100644 index 0000000000000..b69260d8d4157 --- /dev/null +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 } from 'kibana/public'; +import { + SharePluginSetup, + UrlGeneratorsDefinition, + UrlGeneratorState, +} from '../../../../../src/plugins/share/public'; +import { MlStartDependencies } from '../plugin'; +import { ML_PAGES, ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator'; +import { MlUrlGeneratorState } from '../../common/types/ml_url_generator'; +import { + createAnomalyDetectionJobManagementUrl, + createAnomalyDetectionCreateJobSelectType, + createExplorerUrl, + createSingleMetricViewerUrl, +} from './anomaly_detection_urls_generator'; +import { + createDataFrameAnalyticsJobManagementUrl, + createDataFrameAnalyticsExplorationUrl, +} from './data_frame_analytics_urls_generator'; +import { + createIndexDataVisualizerUrl, + createDataVisualizerUrl, +} from './data_visualizer_urls_generator'; + +declare module '../../../../../src/plugins/share/public' { + export interface UrlGeneratorStateMapping { + [ML_APP_URL_GENERATOR]: UrlGeneratorState; + } +} + +interface Params { + appBasePath: string; + useHash: boolean; +} + +export class MlUrlGenerator implements UrlGeneratorsDefinition { + constructor(private readonly params: Params) {} + + public readonly id = ML_APP_URL_GENERATOR; + + public readonly createUrl = async (mlUrlGeneratorState: MlUrlGeneratorState): Promise => { + const appBasePath = this.params.appBasePath; + switch (mlUrlGeneratorState.page) { + case ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE: + return createAnomalyDetectionJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState); + case ML_PAGES.ANOMALY_EXPLORER: + return createExplorerUrl(appBasePath, mlUrlGeneratorState.pageState); + case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: + return createAnomalyDetectionCreateJobSelectType( + appBasePath, + mlUrlGeneratorState.pageState + ); + case ML_PAGES.SINGLE_METRIC_VIEWER: + return createSingleMetricViewerUrl(appBasePath, mlUrlGeneratorState.pageState); + case ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE: + return createDataFrameAnalyticsJobManagementUrl(appBasePath, mlUrlGeneratorState.pageState); + case ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION: + return createDataFrameAnalyticsExplorationUrl(appBasePath, mlUrlGeneratorState.pageState); + case ML_PAGES.DATA_VISUALIZER: + case ML_PAGES.DATA_VISUALIZER_FILE: + case ML_PAGES.DATA_VISUALIZER_INDEX_SELECT: + return createDataVisualizerUrl(appBasePath, mlUrlGeneratorState); + case ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER: + return createIndexDataVisualizerUrl(appBasePath, mlUrlGeneratorState.pageState); + default: + throw new Error('Page type is not provided or unknown'); + } + }; +} + +/** + * Registers the URL generator + */ +export function registerUrlGenerator( + share: SharePluginSetup, + core: CoreSetup +) { + const baseUrl = core.http.basePath.prepend('/app/ml'); + share.urlGenerators.registerUrlGenerator( + new MlUrlGenerator({ + appBasePath: baseUrl, + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); +} diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 214b393a0fda9..3e8ab99e341ad 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -34,7 +34,7 @@ import { registerFeature } from './register_feature'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; import { registerMlUiActions } from './ui_actions'; import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; -import { registerUrlGenerator } from './url_generator'; +import { registerUrlGenerator } from './ml_url_generator'; import { isFullLicense, isMlEnabled } from '../common/license'; import { registerEmbeddables } from './embeddables'; diff --git a/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx b/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx index e327befcf7293..e4d7cfa32c2cd 100644 --- a/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/open_in_anomaly_explorer_action.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { ActionContextMapping, createAction } from '../../../../../src/plugins/ui_actions/public'; import { MlCoreSetup } from '../plugin'; -import { ML_APP_URL_GENERATOR } from '../url_generator'; +import { ML_APP_URL_GENERATOR } from '../../common/constants/ml_url_generator'; import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, SwimLaneDrilldownContext } from '../embeddables'; export const OPEN_IN_ANOMALY_EXPLORER_ACTION = 'openInAnomalyExplorerAction'; @@ -32,19 +32,21 @@ export function createOpenInExplorerAction(getStartServices: MlCoreSetup['getSta return urlGenerator.createUrl({ page: 'explorer', - jobIds, - timeRange, - mlExplorerSwimlane: { - viewByFromPage: fromPage, - viewByPerPage: perPage, - viewByFieldName: viewBy, - ...(data - ? { - selectedType: data.type, - selectedTimes: data.times, - selectedLanes: data.lanes, - } - : {}), + pageState: { + jobIds, + timeRange, + mlExplorerSwimlane: { + viewByFromPage: fromPage, + viewByPerPage: perPage, + viewByFieldName: viewBy, + ...(data + ? { + selectedType: data.type, + selectedTimes: data.times, + selectedLanes: data.lanes, + } + : {}), + }, }, }); }, diff --git a/x-pack/plugins/ml/public/url_generator.test.ts b/x-pack/plugins/ml/public/url_generator.test.ts deleted file mode 100644 index 21dde12404957..0000000000000 --- a/x-pack/plugins/ml/public/url_generator.test.ts +++ /dev/null @@ -1,34 +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 { MlUrlGenerator } from './url_generator'; - -describe('MlUrlGenerator', () => { - const urlGenerator = new MlUrlGenerator({ - appBasePath: '/app/ml', - useHash: false, - }); - - it('should generate valid URL for the Anomaly Explorer page', async () => { - const url = await urlGenerator.createUrl({ - page: 'explorer', - jobIds: ['test-job'], - mlExplorerSwimlane: { viewByFromPage: 2, viewByPerPage: 20 }, - }); - expect(url).toBe( - '/app/ml/explorer#?_g=(ml:(jobIds:!(test-job)))&_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20))' - ); - }); - - it('should throw an error in case the page is not provided', async () => { - expect.assertions(1); - - // @ts-ignore - await urlGenerator.createUrl({ jobIds: ['test-job'] }).catch((e) => { - expect(e.message).toEqual('Page type is not provided or unknown'); - }); - }); -}); diff --git a/x-pack/plugins/ml/public/url_generator.ts b/x-pack/plugins/ml/public/url_generator.ts deleted file mode 100644 index 4e08c57c0b2e0..0000000000000 --- a/x-pack/plugins/ml/public/url_generator.ts +++ /dev/null @@ -1,118 +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 { CoreSetup } from 'kibana/public'; -import { - SharePluginSetup, - UrlGeneratorsDefinition, - UrlGeneratorState, -} from '../../../../src/plugins/share/public'; -import { TimeRange } from '../../../../src/plugins/data/public'; -import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; -import { JobId } from '../../reporting/common/types'; -import { ExplorerAppState } from './application/explorer/explorer_dashboard_service'; -import { MlStartDependencies } from './plugin'; - -declare module '../../../../src/plugins/share/public' { - export interface UrlGeneratorStateMapping { - [ML_APP_URL_GENERATOR]: UrlGeneratorState; - } -} - -export const ML_APP_URL_GENERATOR = 'ML_APP_URL_GENERATOR'; - -export interface ExplorerUrlState { - /** - * ML App Page - */ - page: 'explorer'; - /** - * Job IDs - */ - jobIds: JobId[]; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - /** - * Optional state for the swim lane - */ - mlExplorerSwimlane?: ExplorerAppState['mlExplorerSwimlane']; - mlExplorerFilter?: ExplorerAppState['mlExplorerFilter']; -} - -/** - * Union type of ML URL state based on page - */ -export type MlUrlGeneratorState = ExplorerUrlState; - -export interface ExplorerQueryState { - ml: { jobIds: JobId[] }; - time?: TimeRange; -} - -interface Params { - appBasePath: string; - useHash: boolean; -} - -export class MlUrlGenerator implements UrlGeneratorsDefinition { - constructor(private readonly params: Params) {} - - public readonly id = ML_APP_URL_GENERATOR; - - public readonly createUrl = async ({ page, ...params }: MlUrlGeneratorState): Promise => { - if (page === 'explorer') { - return this.createExplorerUrl(params); - } - throw new Error('Page type is not provided or unknown'); - }; - - /** - * Creates URL to the Anomaly Explorer page - */ - private createExplorerUrl({ - timeRange, - jobIds, - mlExplorerSwimlane = {}, - mlExplorerFilter = {}, - }: Omit): string { - const appState: ExplorerAppState = { - mlExplorerSwimlane, - mlExplorerFilter, - }; - - const queryState: ExplorerQueryState = { - ml: { - jobIds, - }, - }; - - if (timeRange) queryState.time = timeRange; - - let url = `${this.params.appBasePath}/explorer`; - url = setStateToKbnUrl('_g', queryState, { useHash: false }, url); - url = setStateToKbnUrl('_a', appState, { useHash: false }, url); - - return url; - } -} - -/** - * Registers the URL generator - */ -export function registerUrlGenerator( - share: SharePluginSetup, - core: CoreSetup -) { - const baseUrl = core.http.basePath.prepend('/app/ml'); - share.urlGenerators.registerUrlGenerator( - new MlUrlGenerator({ - appBasePath: baseUrl, - useHash: core.uiSettings.get('state:storeInSessionStorage'), - }) - ); -} diff --git a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts index 902cc907a0eff..28f12ead7924b 100644 --- a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts +++ b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts @@ -18,7 +18,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) addAppLinksToSampleDataset('ecommerce', [ { path: - '/app/ml#/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f', + '/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f', label: sampleDataLinkLabel, icon: 'machineLearningApp', }, @@ -27,7 +27,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) addAppLinksToSampleDataset('logs', [ { path: - '/app/ml#/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247', + '/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247', label: sampleDataLinkLabel, icon: 'machineLearningApp', }, From c46e77712ae6ae0ae563d0ebc9b6ca53ef2bccbd Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 2 Sep 2020 16:44:42 -0400 Subject: [PATCH 13/43] [Security Solution][Exceptions] - Adds sort order for exception items (#76537) ## Summary **Components affected:** ExceptionsViewer **Current behavior:** - when a user edits an exception item, the order of the exception items in the viewer changes. This creates confusion and looks like updates weren't applied (even though they were, just item order changed) **New behavior:** - when a user edits an exception item, the order of the exception items in the viewer don't change. Sort order is now based on `created_at` --- x-pack/plugins/lists/public/exceptions/api.test.ts | 10 ++++++++++ x-pack/plugins/lists/public/exceptions/api.ts | 2 ++ 2 files changed, 12 insertions(+) diff --git a/x-pack/plugins/lists/public/exceptions/api.test.ts b/x-pack/plugins/lists/public/exceptions/api.test.ts index 7e79b212f69cb..b02a82f98af91 100644 --- a/x-pack/plugins/lists/public/exceptions/api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/api.test.ts @@ -375,6 +375,8 @@ describe('Exceptions Lists API', () => { namespace_type: 'single,single', page: '1', per_page: '20', + sort_field: 'created_at', + sort_order: 'desc', }, signal: abortCtrl.signal, }); @@ -406,6 +408,8 @@ describe('Exceptions Lists API', () => { namespace_type: 'single', page: '1', per_page: '20', + sort_field: 'created_at', + sort_order: 'desc', }, signal: abortCtrl.signal, }); @@ -437,6 +441,8 @@ describe('Exceptions Lists API', () => { namespace_type: 'agnostic', page: '1', per_page: '20', + sort_field: 'created_at', + sort_order: 'desc', }, signal: abortCtrl.signal, }); @@ -468,6 +474,8 @@ describe('Exceptions Lists API', () => { namespace_type: 'agnostic', page: '1', per_page: '20', + sort_field: 'created_at', + sort_order: 'desc', }, signal: abortCtrl.signal, }); @@ -500,6 +508,8 @@ describe('Exceptions Lists API', () => { namespace_type: 'agnostic', page: '1', per_page: '20', + sort_field: 'created_at', + sort_order: 'desc', }, signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/lists/public/exceptions/api.ts b/x-pack/plugins/lists/public/exceptions/api.ts index 203c84b2943fd..3f5ec80320503 100644 --- a/x-pack/plugins/lists/public/exceptions/api.ts +++ b/x-pack/plugins/lists/public/exceptions/api.ts @@ -288,6 +288,8 @@ export const fetchExceptionListsItemsByListIds = async ({ namespace_type: namespaceTypes.join(','), page: pagination.page ? `${pagination.page}` : '1', per_page: pagination.perPage ? `${pagination.perPage}` : '20', + sort_field: 'created_at', + sort_order: 'desc', ...(filters.trim() !== '' ? { filter: filters } : {}), }; const [validatedRequest, errorsRequest] = validate(query, findExceptionListItemSchema); From 5467f3164034784f10e60c084dbcc856ec0b4b36 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 2 Sep 2020 16:00:23 -0500 Subject: [PATCH 14/43] Remove dependencies from index pattern field and index pattern list (#75185) * Remove dependencies from index pattern field and index pattern list --- ...ins-data-public.fieldlist._constructor_.md | 23 --- ...lugin-plugins-data-public.fieldlist.add.md | 11 - ...in-plugins-data-public.fieldlist.getall.md | 11 - ...plugins-data-public.fieldlist.getbyname.md | 11 - ...plugins-data-public.fieldlist.getbytype.md | 11 - ...na-plugin-plugins-data-public.fieldlist.md | 27 +-- ...in-plugins-data-public.fieldlist.remove.md | 11 - ...plugins-data-public.fieldlist.removeall.md | 11 - ...lugins-data-public.fieldlist.replaceall.md | 11 - ...in-plugins-data-public.fieldlist.tospec.md | 25 --- ...in-plugins-data-public.fieldlist.update.md | 11 - ...a-plugin-plugins-data-public.ifieldtype.md | 2 +- ...n-plugins-data-public.ifieldtype.tospec.md | 4 +- ...gins-data-public.iindexpatternfieldlist.md | 1 + ...ta-public.iindexpatternfieldlist.tospec.md | 24 +++ ...-public.indexpatternfield._constructor_.md | 4 +- ...ns-data-public.indexpatternfield.format.md | 11 - ...a-public.indexpatternfield.indexpattern.md | 11 - ...n-plugins-data-public.indexpatternfield.md | 6 +- ...ns-data-public.indexpatternfield.tospec.md | 21 +- .../kibana-plugin-plugins-data-public.md | 2 +- ...a-plugin-plugins-data-server.ifieldtype.md | 2 +- ...n-plugins-data-server.ifieldtype.tospec.md | 4 +- src/core/public/public.api.md | 3 + src/core/server/server.api.md | 3 + .../es_query/filters/get_display_value.ts | 30 ++- .../common/field_formats/converters/source.ts | 4 +- .../field_formats/field_formats_registry.ts | 6 +- .../data/common/field_formats/types.ts | 1 + .../data/common/index_patterns/errors.ts | 29 +++ .../index_pattern_field.test.ts.snap | 8 +- .../index_patterns/fields/field_list.ts | 143 +++++++------ .../fields/index_pattern_field.test.ts | 46 ++--- .../fields/index_pattern_field.ts | 39 ++-- .../common/index_patterns/fields/types.ts | 4 +- .../data/common/index_patterns/index.ts | 2 + .../__snapshots__/index_pattern.test.ts.snap | 189 +++++++++++++++--- .../index_patterns/format_hit.ts | 4 +- .../index_patterns/index_pattern.test.ts | 12 +- .../index_patterns/index_pattern.ts | 86 ++++++-- .../index_patterns/index_patterns.ts | 10 - .../common/search/aggs/agg_config.test.ts | 6 +- .../data/common/search/aggs/agg_type.test.ts | 3 + .../data/common/search/aggs/agg_type.ts | 4 +- .../buckets/_terms_other_bucket_helper.ts | 8 +- .../buckets/create_filter/histogram.test.ts | 1 + .../aggs/buckets/create_filter/range.test.ts | 2 +- .../aggs/buckets/create_filter/terms.ts | 2 +- .../common/search/aggs/buckets/date_range.ts | 4 +- .../common/search/aggs/buckets/ip_range.ts | 4 +- .../common/search/aggs/buckets/range.test.ts | 13 +- .../data/common/search/aggs/buckets/range.ts | 4 +- .../data/common/search/aggs/buckets/terms.ts | 4 +- .../aggs/metrics/parent_pipeline.test.ts | 6 +- .../aggs/metrics/sibling_pipeline.test.ts | 3 + .../create_filters_from_value_click.test.ts | 6 +- src/plugins/data/public/index.ts | 2 +- src/plugins/data/public/public.api.md | 64 ++---- .../search/expressions/create_filter.test.ts | 1 + src/plugins/data/server/server.api.md | 5 +- .../sidebar/discover_field.test.tsx | 4 +- .../components/sidebar/discover_sidebar.tsx | 4 +- .../sidebar/lib/field_calculator.js | 6 +- .../sidebar/lib/field_calculator.test.ts | 12 +- .../components/sidebar/lib/get_details.ts | 8 +- .../lib/get_index_pattern_field_list.ts | 1 + .../indexed_fields_table.test.tsx.snap | 19 +- .../indexed_fields_table.test.tsx | 8 +- .../indexed_fields_table.tsx | 1 - .../edit_index_pattern/tabs/tabs.tsx | 4 +- .../edit_index_pattern/tabs/utils.ts | 4 +- .../field_editor/field_editor.test.tsx | 6 +- .../public/control/control.ts | 5 +- src/test_utils/public/stub_index_pattern.js | 4 +- .../classes/sources/es_source/es_source.js | 2 +- .../classes/tooltips/es_tooltip_property.ts | 9 +- .../services/field_format_service.ts | 6 +- .../rules/description_step/helpers.test.tsx | 6 +- 78 files changed, 578 insertions(+), 547 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md create mode 100644 src/plugins/data/common/index_patterns/errors.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md deleted file mode 100644 index 9f9613a5a68f7..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist._constructor_.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [(constructor)](./kibana-plugin-plugins-data-public.fieldlist._constructor_.md) - -## FieldList.(constructor) - -Constructs a new instance of the `FieldList` class - -Signature: - -```typescript -constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean, onNotification?: OnNotification); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| indexPattern | IndexPattern | | -| specs | FieldSpec[] | | -| shortDotsEnable | boolean | | -| onNotification | OnNotification | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md deleted file mode 100644 index ae3d82f0cc3ea..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.add.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [add](./kibana-plugin-plugins-data-public.fieldlist.add.md) - -## FieldList.add property - -Signature: - -```typescript -readonly add: (field: FieldSpec) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md deleted file mode 100644 index da29a4de9acc8..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getAll](./kibana-plugin-plugins-data-public.fieldlist.getall.md) - -## FieldList.getAll property - -Signature: - -```typescript -readonly getAll: () => IndexPatternField[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md deleted file mode 100644 index af368d003423a..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbyname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getByName](./kibana-plugin-plugins-data-public.fieldlist.getbyname.md) - -## FieldList.getByName property - -Signature: - -```typescript -readonly getByName: (name: IndexPatternField['name']) => IndexPatternField | undefined; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md deleted file mode 100644 index 16bae3ee7c555..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.getbytype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [getByType](./kibana-plugin-plugins-data-public.fieldlist.getbytype.md) - -## FieldList.getByType property - -Signature: - -```typescript -readonly getByType: (type: IndexPatternField['type']) => any[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md index 012b069430290..79bcaf9700cf0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.md @@ -1,32 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [fieldList](./kibana-plugin-plugins-data-public.fieldlist.md) -## FieldList class +## fieldList variable Signature: ```typescript -export declare class FieldList extends Array implements IIndexPatternFieldList +fieldList: (specs?: FieldSpec[], shortDotsEnable?: boolean) => IIndexPatternFieldList ``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(indexPattern, specs, shortDotsEnable, onNotification)](./kibana-plugin-plugins-data-public.fieldlist._constructor_.md) | | Constructs a new instance of the FieldList class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [add](./kibana-plugin-plugins-data-public.fieldlist.add.md) | | (field: FieldSpec) => void | | -| [getAll](./kibana-plugin-plugins-data-public.fieldlist.getall.md) | | () => IndexPatternField[] | | -| [getByName](./kibana-plugin-plugins-data-public.fieldlist.getbyname.md) | | (name: IndexPatternField['name']) => IndexPatternField | undefined | | -| [getByType](./kibana-plugin-plugins-data-public.fieldlist.getbytype.md) | | (type: IndexPatternField['type']) => any[] | | -| [remove](./kibana-plugin-plugins-data-public.fieldlist.remove.md) | | (field: IFieldType) => void | | -| [removeAll](./kibana-plugin-plugins-data-public.fieldlist.removeall.md) | | () => void | | -| [replaceAll](./kibana-plugin-plugins-data-public.fieldlist.replaceall.md) | | (specs: FieldSpec[]) => void | | -| [toSpec](./kibana-plugin-plugins-data-public.fieldlist.tospec.md) | | () => {
count: number;
script: string | undefined;
lang: string | undefined;
conflictDescriptions: Record<string, string[]> | undefined;
name: string;
type: string;
esTypes: string[] | undefined;
scripted: boolean;
searchable: boolean;
aggregatable: boolean;
readFromDocValues: boolean;
subType: import("../types").IFieldSubType | undefined;
format: any;
}[] | | -| [update](./kibana-plugin-plugins-data-public.fieldlist.update.md) | | (field: FieldSpec) => void | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md deleted file mode 100644 index 149410adb3550..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.remove.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [remove](./kibana-plugin-plugins-data-public.fieldlist.remove.md) - -## FieldList.remove property - -Signature: - -```typescript -readonly remove: (field: IFieldType) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md deleted file mode 100644 index 92a45349ad005..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.removeall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [removeAll](./kibana-plugin-plugins-data-public.fieldlist.removeall.md) - -## FieldList.removeAll property - -Signature: - -```typescript -readonly removeAll: () => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md deleted file mode 100644 index 5330440e6b96a..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.replaceall.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [replaceAll](./kibana-plugin-plugins-data-public.fieldlist.replaceall.md) - -## FieldList.replaceAll property - -Signature: - -```typescript -readonly replaceAll: (specs: FieldSpec[]) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md deleted file mode 100644 index e646339feb495..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.tospec.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [toSpec](./kibana-plugin-plugins-data-public.fieldlist.tospec.md) - -## FieldList.toSpec property - -Signature: - -```typescript -readonly toSpec: () => { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: any; - }[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md deleted file mode 100644 index c718e47b31b50..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldlist.update.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) > [update](./kibana-plugin-plugins-data-public.fieldlist.update.md) - -## FieldList.update property - -Signature: - -```typescript -readonly update: (field: FieldSpec) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md index 6f42fb32fdb7b..3ff2afafcc514 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md @@ -28,7 +28,7 @@ export interface IFieldType | [searchable](./kibana-plugin-plugins-data-public.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-public.ifieldtype.sortable.md) | boolean | | | [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md) | IFieldSubType | | -| [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | () => FieldSpec | | +| [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | | [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-public.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md index 1fb4084c25d34..52238ea2a00ca 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.tospec.md @@ -7,5 +7,7 @@ Signature: ```typescript -toSpec?: () => FieldSpec; +toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md index b068c4804c0dd..b1e13ffaabd07 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md @@ -21,5 +21,6 @@ export interface IIndexPatternFieldList extends Array | [remove(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md) | | | [removeAll()](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.removeall.md) | | | [replaceAll(specs)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.replaceall.md) | | +| [toSpec(options)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md) | | | [update(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md new file mode 100644 index 0000000000000..fd20f2944c5be --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [toSpec](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.tospec.md) + +## IIndexPatternFieldList.toSpec() method + +Signature: + +```typescript +toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): FieldSpec[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | {
getFormatterForField?: IndexPattern['getFormatterForField'];
} | | + +Returns: + +`FieldSpec[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md index 10b65bdccdf87..5d467a7a9cbce 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md @@ -9,15 +9,13 @@ Constructs a new instance of the `IndexPatternField` class Signature: ```typescript -constructor(indexPattern: IndexPattern, spec: FieldSpec, displayName: string, onNotification: OnNotification); +constructor(spec: FieldSpec, displayName: string); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| indexPattern | IndexPattern | | | spec | FieldSpec | | | displayName | string | | -| onNotification | OnNotification | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md deleted file mode 100644 index f28d5b1bca7e5..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.format.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) - -## IndexPatternField.format property - -Signature: - -```typescript -get format(): FieldFormat; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md deleted file mode 100644 index 3d145cce9d07d..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) - -## IndexPatternField.indexPattern property - -Signature: - -```typescript -readonly indexPattern: IndexPattern; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 713b29ea3a3d3..215188ffa2607 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -14,7 +14,7 @@ export declare class IndexPatternField implements IFieldType | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(indexPattern, spec, displayName, onNotification)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | +| [(constructor)(spec, displayName)](./kibana-plugin-plugins-data-public.indexpatternfield._constructor_.md) | | Constructs a new instance of the IndexPatternField class | ## Properties @@ -26,8 +26,6 @@ export declare class IndexPatternField implements IFieldType | [displayName](./kibana-plugin-plugins-data-public.indexpatternfield.displayname.md) | | string | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | undefined | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | -| [format](./kibana-plugin-plugins-data-public.indexpatternfield.format.md) | | FieldFormat | | -| [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | IndexPattern | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | undefined | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | | [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | @@ -45,5 +43,5 @@ export declare class IndexPatternField implements IFieldType | Method | Modifiers | Description | | --- | --- | --- | | [toJSON()](./kibana-plugin-plugins-data-public.indexpatternfield.tojson.md) | | | -| [toSpec()](./kibana-plugin-plugins-data-public.indexpatternfield.tospec.md) | | | +| [toSpec({ getFormatterForField, })](./kibana-plugin-plugins-data-public.indexpatternfield.tospec.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md index 5037cb0049e82..1d80c90991f55 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tospec.md @@ -7,7 +7,9 @@ Signature: ```typescript -toSpec(): { +toSpec({ getFormatterForField, }?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): { count: number; script: string | undefined; lang: string | undefined; @@ -20,9 +22,19 @@ toSpec(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }; ``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { getFormatterForField, } | {
getFormatterForField?: IndexPattern['getFormatterForField'];
} | | + Returns: `{ @@ -38,6 +50,9 @@ toSpec(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 09702df4fdb54..0ab86e0f4dab2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -10,7 +10,6 @@ | --- | --- | | [AggParamType](./kibana-plugin-plugins-data-public.aggparamtype.md) | | | [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | | -| [FieldList](./kibana-plugin-plugins-data-public.fieldlist.md) | | | [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | | | [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) | | | [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) | | @@ -103,6 +102,7 @@ | [expandShorthand](./kibana-plugin-plugins-data-public.expandshorthand.md) | | | [extractSearchSourceReferences](./kibana-plugin-plugins-data-public.extractsearchsourcereferences.md) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.md) | | +| [fieldList](./kibana-plugin-plugins-data-public.fieldlist.md) | | | [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | | | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md index 77a2954428f8d..d106f3a35a91c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md @@ -28,7 +28,7 @@ export interface IFieldType | [searchable](./kibana-plugin-plugins-data-server.ifieldtype.searchable.md) | boolean | | | [sortable](./kibana-plugin-plugins-data-server.ifieldtype.sortable.md) | boolean | | | [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md) | IFieldSubType | | -| [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | () => FieldSpec | | +| [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec | | | [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md) | string | | | [visualizable](./kibana-plugin-plugins-data-server.ifieldtype.visualizable.md) | boolean | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md index d1863bebce4f0..6f8ee9d9eebf0 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.tospec.md @@ -7,5 +7,7 @@ Signature: ```typescript -toSpec?: () => FieldSpec; +toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; ``` diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index bacbd6e757114..cc37a3c7c0924 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -7,6 +7,7 @@ import { Action } from 'history'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import Boom from 'boom'; +import { ErrorToastOptions as ErrorToastOptions_2 } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; @@ -27,7 +28,9 @@ import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/ser import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import * as Rx from 'rxjs'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { ShallowPromise } from '@kbn/utility-types'; +import { ToastInputFields as ToastInputFields_2 } from 'src/core/public/notifications'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 05afad5a4f7a4..2128eb077211f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -40,6 +40,7 @@ import { DeleteScriptParams } from 'elasticsearch'; import { DeleteTemplateParams } from 'elasticsearch'; import { DetailedPeerCertificate } from 'tls'; import { Duration } from 'moment'; +import { ErrorToastOptions } from 'src/core/public/notifications'; import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { FieldStatsParams } from 'elasticsearch'; @@ -118,6 +119,7 @@ import { RenderSearchTemplateParams } from 'elasticsearch'; import { Request } from 'hapi'; import { ResponseObject } from 'hapi'; import { ResponseToolkit } from 'hapi'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; @@ -141,6 +143,7 @@ import { TasksCancelParams } from 'elasticsearch'; import { TasksGetParams } from 'elasticsearch'; import { TasksListParams } from 'elasticsearch'; import { TermvectorsParams } from 'elasticsearch'; +import { ToastInputFields } from 'src/core/public/notifications'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; diff --git a/src/plugins/data/common/es_query/filters/get_display_value.ts b/src/plugins/data/common/es_query/filters/get_display_value.ts index 10b4dab3f46ef..28ba0ab629e8f 100644 --- a/src/plugins/data/common/es_query/filters/get_display_value.ts +++ b/src/plugins/data/common/es_query/filters/get_display_value.ts @@ -17,30 +17,26 @@ * under the License. */ -import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IIndexPattern, IFieldType } from '../..'; +import { IIndexPattern } from '../..'; import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; import { Filter } from '../filters'; function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { - if (!indexPattern || !key) return; + // checking getFormatterForField exists because there is at least once case where an index pattern + // is an object rather than an IndexPattern class + if (!indexPattern || !indexPattern.getFormatterForField || !key) return; - let format = get(indexPattern, ['fields', 'byName', key, 'format']); - if (!format && (indexPattern.fields as any).getByName) { - // TODO: Why is indexPatterns sometimes a map and sometimes an array? - const field: IFieldType = (indexPattern.fields as any).getByName(key); - if (!field) { - throw new Error( - i18n.translate('data.filter.filterBar.fieldNotFound', { - defaultMessage: 'Field {key} not found in index pattern {indexPattern}', - values: { key, indexPattern: indexPattern.title }, - }) - ); - } - format = field.format; + const field = indexPattern.fields.find((f) => f.name === key); + if (!field) { + throw new Error( + i18n.translate('data.filter.filterBar.fieldNotFound', { + defaultMessage: 'Field {key} not found in index pattern {indexPattern}', + values: { key, indexPattern: indexPattern.title }, + }) + ); } - return format; + return indexPattern.getFormatterForField(field); } export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { diff --git a/src/plugins/data/common/field_formats/converters/source.ts b/src/plugins/data/common/field_formats/converters/source.ts index 9c81bb011e127..e7dce82c725d2 100644 --- a/src/plugins/data/common/field_formats/converters/source.ts +++ b/src/plugins/data/common/field_formats/converters/source.ts @@ -60,7 +60,7 @@ export class SourceFormat extends FieldFormat { textConvert: TextContextTypeConvert = (value) => JSON.stringify(value); htmlConvert: HtmlContextTypeConvert = (value, options = {}) => { - const { field, hit } = options; + const { field, hit, indexPattern } = options; if (!field) { const converter = this.getConverterFor('text') as Function; @@ -69,7 +69,7 @@ export class SourceFormat extends FieldFormat { } const highlights = (hit && hit.highlight) || {}; - const formatted = field.indexPattern.formatHit(hit); + const formatted = indexPattern.formatHit(hit); const highlightPairs: any[] = []; const sourcePairs: any[] = []; const isShortDots = this.getConfig!(UI_SETTINGS.SHORT_DOTS_ENABLE); diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index 4b46adf399363..dbc3693c99779 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -181,11 +181,11 @@ export class FieldFormatsRegistry { * @param {ES_FIELD_TYPES[]} esTypes * @return {FieldFormat} */ - getDefaultInstancePlain( + getDefaultInstancePlain = ( fieldType: KBN_FIELD_TYPES, esTypes?: ES_FIELD_TYPES[], params: Record = {} - ): FieldFormat { + ): FieldFormat => { const conf = this.getDefaultConfig(fieldType, esTypes); const instanceParams = { ...conf.params, @@ -193,7 +193,7 @@ export class FieldFormatsRegistry { }; return this.getInstance(conf.id, instanceParams); - } + }; /** * Returns a cache key built by the given variables for caching in memoized * Where esType contains fieldType, fieldType is returned diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index daa44b2b0f85b..af956a20c0dc5 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -27,6 +27,7 @@ export type FieldFormatsContentType = 'html' | 'text'; /** @internal **/ export interface HtmlContextTypeOptions { field?: any; + indexPattern?: any; hit?: Record; } diff --git a/src/plugins/data/common/index_patterns/errors.ts b/src/plugins/data/common/index_patterns/errors.ts new file mode 100644 index 0000000000000..3d92bae1968fb --- /dev/null +++ b/src/plugins/data/common/index_patterns/errors.ts @@ -0,0 +1,29 @@ +/* + * 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 { FieldSpec } from './types'; + +export class FieldTypeUnknownError extends Error { + public readonly fieldSpec: FieldSpec; + constructor(message: string, spec: FieldSpec) { + super(message); + this.name = 'FieldTypeUnknownError'; + this.fieldSpec = spec; + } +} diff --git a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap index e61593f6bfb27..4279dd320ad62 100644 --- a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap +++ b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap @@ -14,7 +14,7 @@ Object { }, "count": 1, "esTypes": Array [ - "type", + "text", ], "lang": "lang", "name": "name", @@ -30,7 +30,7 @@ Object { "path": "path", }, }, - "type": "type", + "type": "string", } `; @@ -48,7 +48,7 @@ Object { }, "count": 1, "esTypes": Array [ - "type", + "text", ], "format": Object { "id": "number", @@ -70,6 +70,6 @@ Object { "path": "path", }, }, - "type": "type", + "type": "string", } `; diff --git a/src/plugins/data/common/index_patterns/fields/field_list.ts b/src/plugins/data/common/index_patterns/fields/field_list.ts index d2489a5d1f7e3..4cf6075869851 100644 --- a/src/plugins/data/common/index_patterns/fields/field_list.ts +++ b/src/plugins/data/common/index_patterns/fields/field_list.ts @@ -35,6 +35,7 @@ export interface IIndexPatternFieldList extends Array { removeAll(): void; replaceAll(specs: FieldSpec[]): void; update(field: FieldSpec): void; + toSpec(options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }): FieldSpec[]; } export type CreateIndexPatternFieldList = ( @@ -44,87 +45,79 @@ export type CreateIndexPatternFieldList = ( onNotification?: OnNotification ) => IIndexPatternFieldList; -export class FieldList extends Array implements IIndexPatternFieldList { - private byName: FieldMap = new Map(); - private groups: Map = new Map(); - private indexPattern: IndexPattern; - private shortDotsEnable: boolean; - private onNotification: OnNotification; - private setByName = (field: IndexPatternField) => this.byName.set(field.name, field); - private setByGroup = (field: IndexPatternField) => { - if (typeof this.groups.get(field.type) === 'undefined') { - this.groups.set(field.type, new Map()); +// extending the array class and using a constructor doesn't work well +// when calling filter and similar so wrapping in a callback. +// to be removed in the future +export const fieldList = ( + specs: FieldSpec[] = [], + shortDotsEnable = false +): IIndexPatternFieldList => { + class FldList extends Array implements IIndexPatternFieldList { + private byName: FieldMap = new Map(); + private groups: Map = new Map(); + private setByName = (field: IndexPatternField) => this.byName.set(field.name, field); + private setByGroup = (field: IndexPatternField) => { + if (typeof this.groups.get(field.type) === 'undefined') { + this.groups.set(field.type, new Map()); + } + this.groups.get(field.type)!.set(field.name, field); + }; + private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); + private calcDisplayName = (name: string) => + shortDotsEnable ? shortenDottedString(name) : name; + constructor() { + super(); + specs.map((field) => this.add(field)); } - this.groups.get(field.type)!.set(field.name, field); - }; - private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); - private calcDisplayName = (name: string) => - this.shortDotsEnable ? shortenDottedString(name) : name; - constructor( - indexPattern: IndexPattern, - specs: FieldSpec[] = [], - shortDotsEnable = false, - onNotification: OnNotification = () => {} - ) { - super(); - this.indexPattern = indexPattern; - this.shortDotsEnable = shortDotsEnable; - this.onNotification = onNotification; - specs.map((field) => this.add(field)); - } + public readonly getAll = () => [...this.byName.values()]; + public readonly getByName = (name: IndexPatternField['name']) => this.byName.get(name); + public readonly getByType = (type: IndexPatternField['type']) => [ + ...(this.groups.get(type) || new Map()).values(), + ]; + public readonly add = (field: FieldSpec) => { + const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + this.push(newField); + this.setByName(newField); + this.setByGroup(newField); + }; - public readonly getAll = () => [...this.byName.values()]; - public readonly getByName = (name: IndexPatternField['name']) => this.byName.get(name); - public readonly getByType = (type: IndexPatternField['type']) => [ - ...(this.groups.get(type) || new Map()).values(), - ]; - public readonly add = (field: FieldSpec) => { - const newField = new IndexPatternField( - this.indexPattern, - field, - this.calcDisplayName(field.name), - this.onNotification - ); - this.push(newField); - this.setByName(newField); - this.setByGroup(newField); - }; + public readonly remove = (field: IFieldType) => { + this.removeByGroup(field); + this.byName.delete(field.name); - public readonly remove = (field: IFieldType) => { - this.removeByGroup(field); - this.byName.delete(field.name); + const fieldIndex = findIndex(this, { name: field.name }); + this.splice(fieldIndex, 1); + }; - const fieldIndex = findIndex(this, { name: field.name }); - this.splice(fieldIndex, 1); - }; + public readonly update = (field: FieldSpec) => { + const newField = new IndexPatternField(field, this.calcDisplayName(field.name)); + const index = this.findIndex((f) => f.name === newField.name); + this.splice(index, 1, newField); + this.setByName(newField); + this.removeByGroup(newField); + this.setByGroup(newField); + }; - public readonly update = (field: FieldSpec) => { - const newField = new IndexPatternField( - this.indexPattern, - field, - this.calcDisplayName(field.name), - this.onNotification - ); - const index = this.findIndex((f) => f.name === newField.name); - this.splice(index, 1, newField); - this.setByName(newField); - this.removeByGroup(newField); - this.setByGroup(newField); - }; + public readonly removeAll = () => { + this.length = 0; + this.byName.clear(); + this.groups.clear(); + }; - public readonly removeAll = () => { - this.length = 0; - this.byName.clear(); - this.groups.clear(); - }; + public readonly replaceAll = (spcs: FieldSpec[]) => { + this.removeAll(); + spcs.forEach(this.add); + }; - public readonly replaceAll = (specs: FieldSpec[]) => { - this.removeAll(); - specs.forEach(this.add); - }; + public toSpec({ + getFormatterForField, + }: { + getFormatterForField?: IndexPattern['getFormatterForField']; + } = {}) { + return [...this.map((field) => field.toSpec({ getFormatterForField }))]; + } + } - public readonly toSpec = () => { - return [...this.map((field) => field.toSpec())]; - }; -} + return new FldList(); +}; diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts index 0cd0fe8324809..3c4fac81c2c7c 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts @@ -19,7 +19,7 @@ import { IndexPatternField } from './index_pattern_field'; import { IndexPattern } from '../index_patterns'; -import { KBN_FIELD_TYPES } from '../../../common'; +import { KBN_FIELD_TYPES, FieldFormat } from '../../../common'; import { FieldSpec } from '../types'; describe('Field', function () { @@ -28,21 +28,16 @@ describe('Field', function () { } function getField(values = {}) { - return new IndexPatternField( - fieldValues.indexPattern as IndexPattern, - { ...fieldValues, ...values }, - 'displayName', - () => {} - ); + return new IndexPatternField({ ...fieldValues, ...values }, 'displayName'); } const fieldValues = { name: 'name', - type: 'type', + type: 'string', script: 'script', lang: 'lang', count: 1, - esTypes: ['type'], + esTypes: ['text'], aggregatable: true, filterable: true, searchable: true, @@ -125,7 +120,7 @@ describe('Field', function () { const fieldB = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); expect(fieldB.sortable).toEqual(true); - const fieldC = getField({ indexed: false }); + const fieldC = getField({ indexed: false, aggregatable: false, scripted: false }); expect(fieldC.sortable).toEqual(false); }); @@ -139,31 +134,26 @@ describe('Field', function () { const fieldC = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); expect(fieldC.filterable).toEqual(true); - const fieldD = getField({ scripted: false, indexed: false }); + const fieldD = getField({ scripted: false, indexed: false, searchable: false }); expect(fieldD.filterable).toEqual(false); }); it('exports the property to JSON', () => { - const field = new IndexPatternField( - { fieldFormatMap: { name: {} } } as IndexPattern, - fieldValues, - 'displayName', - () => {} - ); + const field = new IndexPatternField(fieldValues, 'displayName'); expect(flatten(field)).toMatchSnapshot(); }); it('spec snapshot', () => { - const field = new IndexPatternField( - { - fieldFormatMap: { - name: { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }) }, - }, - } as IndexPattern, - fieldValues, - 'displayName', - () => {} - ); - expect(field.toSpec()).toMatchSnapshot(); + const field = new IndexPatternField(fieldValues, 'displayName'); + const getFormatterForField = () => + ({ + toJSON: () => ({ + id: 'number', + params: { + pattern: '$0,0.[00]', + }, + }), + } as FieldFormat); + expect(field.toSpec({ getFormatterForField })).toMatchSnapshot(); }); }); diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 965f1a7f63065..7f72bfe55c7cd 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -20,40 +20,27 @@ import { i18n } from '@kbn/i18n'; import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; -import { FieldFormat } from '../../field_formats'; import { IFieldType } from './types'; -import { OnNotification, FieldSpec } from '../types'; - -import { IndexPattern } from '../index_patterns'; +import { FieldSpec, IndexPattern } from '../..'; +import { FieldTypeUnknownError } from '../errors'; export class IndexPatternField implements IFieldType { readonly spec: FieldSpec; // not writable or serialized - readonly indexPattern: IndexPattern; readonly displayName: string; private readonly kbnFieldType: KbnFieldType; - constructor( - indexPattern: IndexPattern, - spec: FieldSpec, - displayName: string, - onNotification: OnNotification - ) { - this.indexPattern = indexPattern; + constructor(spec: FieldSpec, displayName: string) { this.spec = { ...spec, type: spec.name === '_source' ? '_source' : spec.type }; this.displayName = displayName; this.kbnFieldType = getKbnFieldType(spec.type); if (spec.type && this.kbnFieldType?.name === KBN_FIELD_TYPES.UNKNOWN) { - const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { - values: { type: spec.type }, - defaultMessage: 'Unknown field type {type}', - }); - const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { - values: { name: spec.name, title: indexPattern.title }, - defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', + const msg = i18n.translate('data.indexPatterns.unknownFieldTypeErrorMsg', { + values: { type: spec.type, name: spec.name }, + defaultMessage: `Field '{name}' Unknown field type '{type}'`, }); - onNotification({ title, text, color: 'danger', iconType: 'alert' }); + throw new FieldTypeUnknownError(msg, spec); } } @@ -143,10 +130,6 @@ export class IndexPatternField implements IFieldType { return this.aggregatable; } - public get format(): FieldFormat { - return this.indexPattern.getFormatterForField(this); - } - public toJSON() { return { count: this.count, @@ -165,7 +148,11 @@ export class IndexPatternField implements IFieldType { }; } - public toSpec() { + public toSpec({ + getFormatterForField, + }: { + getFormatterForField?: IndexPattern['getFormatterForField']; + } = {}) { return { count: this.count, script: this.script, @@ -179,7 +166,7 @@ export class IndexPatternField implements IFieldType { aggregatable: this.aggregatable, readFromDocValues: this.readFromDocValues, subType: this.subType, - format: this.indexPattern?.fieldFormatMap[this.name]?.toJSON() || undefined, + format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined, }; } } diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 558b5b57dce40..5814760601a67 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { FieldSpec, IFieldSubType } from '../types'; +import { FieldSpec, IFieldSubType, IndexPattern } from '../..'; export interface IFieldType { name: string; @@ -38,5 +38,5 @@ export interface IFieldType { subType?: IFieldSubType; displayName?: string; format?: any; - toSpec?: () => FieldSpec; + toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }) => FieldSpec; } diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts index 51a642b775c29..08f478404be2c 100644 --- a/src/plugins/data/common/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index.ts @@ -20,3 +20,5 @@ export * from './fields'; export * from './types'; export { IndexPatternsService } from './index_patterns'; +export type { IndexPattern } from './index_patterns'; +export * from './errors'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 047ac836a87d1..a0c380ec55bf6 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -32,7 +32,12 @@ Object { "esTypes": Array [ "boolean", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "ssl", "readFromDocValues": true, @@ -49,7 +54,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "@timestamp", "readFromDocValues": true, @@ -66,7 +76,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "time", "readFromDocValues": true, @@ -83,7 +98,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "@tags", "readFromDocValues": true, @@ -100,7 +120,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "utc_time", "readFromDocValues": true, @@ -117,7 +142,12 @@ Object { "esTypes": Array [ "integer", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "phpmemory", "readFromDocValues": true, @@ -134,7 +164,12 @@ Object { "esTypes": Array [ "ip", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "ip", "readFromDocValues": true, @@ -151,7 +186,12 @@ Object { "esTypes": Array [ "attachment", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "request_body", "readFromDocValues": true, @@ -168,7 +208,12 @@ Object { "esTypes": Array [ "geo_point", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "point", "readFromDocValues": true, @@ -185,7 +230,12 @@ Object { "esTypes": Array [ "geo_shape", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "area", "readFromDocValues": false, @@ -202,7 +252,12 @@ Object { "esTypes": Array [ "murmur3", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "hashed", "readFromDocValues": false, @@ -219,7 +274,12 @@ Object { "esTypes": Array [ "geo_point", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "geo.coordinates", "readFromDocValues": true, @@ -236,7 +296,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "extension", "readFromDocValues": false, @@ -253,7 +318,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "extension.keyword", "readFromDocValues": true, @@ -274,7 +344,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "machine.os", "readFromDocValues": false, @@ -291,7 +366,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "machine.os.raw", "readFromDocValues": true, @@ -312,7 +392,12 @@ Object { "esTypes": Array [ "keyword", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "geo.src", "readFromDocValues": true, @@ -329,7 +414,12 @@ Object { "esTypes": Array [ "_id", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_id", "readFromDocValues": false, @@ -346,7 +436,12 @@ Object { "esTypes": Array [ "_type", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_type", "readFromDocValues": false, @@ -363,7 +458,12 @@ Object { "esTypes": Array [ "_source", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "_source", "readFromDocValues": false, @@ -380,7 +480,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "non-filterable", "readFromDocValues": false, @@ -397,7 +502,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "non-sortable", "readFromDocValues": false, @@ -414,7 +524,12 @@ Object { "esTypes": Array [ "conflict", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": undefined, "name": "custom_user_field", "readFromDocValues": true, @@ -431,7 +546,12 @@ Object { "esTypes": Array [ "text", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script string", "readFromDocValues": false, @@ -448,7 +568,12 @@ Object { "esTypes": Array [ "long", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script number", "readFromDocValues": false, @@ -465,7 +590,12 @@ Object { "esTypes": Array [ "date", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "painless", "name": "script date", "readFromDocValues": false, @@ -482,7 +612,12 @@ Object { "esTypes": Array [ "murmur3", ], - "format": undefined, + "format": Object { + "id": "number", + "params": Object { + "pattern": "$0,0.[00]", + }, + }, "lang": "expression", "name": "script murmur3", "readFromDocValues": false, diff --git a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts index a0597ed4b9026..b47fef107258a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/format_hit.ts @@ -34,9 +34,9 @@ export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any type: FieldFormatsContentType = 'html' ) { const field = indexPattern.fields.getByName(fieldName); - const format = field ? field.format : defaultFormat; + const format = field ? indexPattern.getFormatterForField(field) : defaultFormat; - return format.convert(val, type, { field, hit }); + return format.convert(val, type, { field, hit, indexPattern }); } function formatHit(hit: Record, type: string = 'html') { diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index f7e1156170f03..2350cbb67683e 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -29,6 +29,7 @@ import { stubbedSavedObjectIndexPattern } from '../../../../../fixtures/stubbed_ import { IndexPatternField } from '../fields'; import { fieldFormatsMock } from '../../field_formats/mocks'; +import { FieldFormat } from '../..'; class MockFieldFormatter {} @@ -170,7 +171,6 @@ describe('IndexPattern', () => { test('should have expected properties on fields', function () { expect(indexPattern.fields[0]).toHaveProperty('displayName'); expect(indexPattern.fields[0]).toHaveProperty('filterable'); - expect(indexPattern.fields[0]).toHaveProperty('format'); expect(indexPattern.fields[0]).toHaveProperty('sortable'); expect(indexPattern.fields[0]).toHaveProperty('scripted'); }); @@ -319,16 +319,18 @@ describe('IndexPattern', () => { describe('toSpec', () => { test('should match snapshot', () => { - indexPattern.fieldFormatMap.bytes = { + const formatter = { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }), - }; + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; expect(indexPattern.toSpec()).toMatchSnapshot(); }); test('can restore from spec', async () => { - indexPattern.fieldFormatMap.bytes = { + const formatter = { toJSON: () => ({ id: 'number', params: { pattern: '$0,0.[00]' } }), - }; + } as FieldFormat; + indexPattern.getFormatterForField = () => formatter; const spec = indexPattern.toSpec(); const restoredPattern = await create(spec.id as string); restoredPattern.initFromSpec(spec); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index ea91a9bb14e1f..c82bf7d759328 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -26,11 +26,12 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, + FieldTypeUnknownError, FieldFormatNotFoundError, } from '../../../common'; import { findByTitle } from '../utils'; import { IndexPatternMissingIndices } from '../lib'; -import { IndexPatternField, IIndexPatternFieldList, FieldList } from '../fields'; +import { IndexPatternField, IIndexPatternFieldList, fieldList } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; @@ -125,8 +126,7 @@ export class IndexPattern implements IIndexPattern { this.shortDotsEnable = shortDotsEnable; this.metaFields = metaFields; - - this.fields = new FieldList(this, [], this.shortDotsEnable, this.onNotification); + this.fields = fieldList([], this.shortDotsEnable); this.apiClient = apiClient; this.fieldsFetcher = createFieldsFetcher(this, apiClient, metaFields); @@ -138,6 +138,22 @@ export class IndexPattern implements IIndexPattern { this.formatField = this.formatHit.formatField; } + private unknownFieldErrorNotification( + fieldType: string, + fieldName: string, + indexPatternTitle: string + ) { + const title = i18n.translate('data.indexPatterns.unknownFieldHeader', { + values: { type: fieldType }, + defaultMessage: 'Unknown field type {type}', + }); + const text = i18n.translate('data.indexPatterns.unknownFieldErrorMessage', { + values: { name: fieldName, title: indexPatternTitle }, + defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', + }); + this.onNotification({ title, text, color: 'danger', iconType: 'alert' }); + } + private serializeFieldFormatMap(flat: any, format: string, field: string | undefined) { if (format && field) { flat[field] = format; @@ -181,7 +197,15 @@ export class IndexPattern implements IIndexPattern { await this.refreshFields(); } else { if (specs) { - this.fields.replaceAll(specs); + try { + this.fields.replaceAll(specs); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } } } } @@ -203,7 +227,15 @@ export class IndexPattern implements IIndexPattern { this.timeFieldName = spec.timeFieldName; this.sourceFilters = spec.sourceFilters; - this.fields.replaceAll(spec.fields || []); + try { + this.fields.replaceAll(spec.fields || []); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } this.typeMeta = spec.typeMeta; this.fieldFormatMap = _.mapValues(fieldFormatMap, (mapping) => { @@ -322,7 +354,7 @@ export class IndexPattern implements IIndexPattern { title: this.title, timeFieldName: this.timeFieldName, sourceFilters: this.sourceFilters, - fields: this.fields.toSpec(), + fields: this.fields.toSpec({ getFormatterForField: this.getFormatterForField.bind(this) }), typeMeta: this.typeMeta, }; } @@ -342,19 +374,27 @@ export class IndexPattern implements IIndexPattern { throw new DuplicateField(name); } - this.fields.add({ - name, - script, - type: fieldType, - scripted: true, - lang, - aggregatable: true, - searchable: true, - count: 0, - readFromDocValues: false, - }); + try { + this.fields.add({ + name, + script, + type: fieldType, + scripted: true, + lang, + aggregatable: true, + searchable: true, + count: 0, + readFromDocValues: false, + }); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } - await this.save(); + await this.save(); + } } removeScriptedField(fieldName: string) { @@ -572,7 +612,15 @@ export class IndexPattern implements IIndexPattern { async _fetchFields() { const fields = await this.fieldsFetcher.fetch(this); const scripted = this.getScriptedFields().map((field) => field.spec); - this.fields.replaceAll([...fields, ...scripted]); + try { + this.fields.replaceAll([...fields, ...scripted]); + } catch (err) { + if (err instanceof FieldTypeUnknownError) { + this.unknownFieldErrorNotification(err.fieldSpec.name, err.fieldSpec.type, this.title); + } else { + throw err; + } + } } refreshFields() { diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 0ad9ae8f2014f..fe0d14b2d9c19 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -25,7 +25,6 @@ import { createEnsureDefaultIndexPattern, EnsureDefaultIndexPattern, } from './ensure_default_index_pattern'; -import { IndexPatternField } from '../fields'; import { OnNotification, OnError, @@ -86,15 +85,6 @@ export class IndexPatternsService { ); } - public createField( - indexPattern: IndexPattern, - spec: IndexPatternField['spec'], - displayName: string, - onNotification: OnNotification - ) { - return new IndexPatternField(indexPattern, spec, displayName, onNotification); - } - private async refreshSavedObjectsCache() { this.savedObjectsCache = await this.savedObjectsClient.find({ type: 'index-pattern', diff --git a/src/plugins/data/common/search/aggs/agg_config.test.ts b/src/plugins/data/common/search/aggs/agg_config.test.ts index a443eacee731c..f6fcc29805dc4 100644 --- a/src/plugins/data/common/search/aggs/agg_config.test.ts +++ b/src/plugins/data/common/search/aggs/agg_config.test.ts @@ -25,8 +25,7 @@ import { AggType } from './agg_type'; import { AggTypesRegistryStart } from './agg_types_registry'; import { mockAggTypesRegistry } from './test_helpers'; import { MetricAggType } from './metrics/metric_agg_type'; -import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern'; -import { IIndexPatternFieldList } from '../../index_patterns/fields'; +import { IndexPattern, IndexPatternField, IIndexPatternFieldList } from '../../index_patterns'; describe('AggConfig', () => { let indexPattern: IndexPattern; @@ -67,6 +66,9 @@ describe('AggConfig', () => { getByName: (name: string) => fields.find((f) => f.name === name), filter: () => fields, } as unknown) as IndexPattern['fields'], + getFormatterForField: (field: IndexPatternField) => ({ + toJSON: () => ({}), + }), } as IndexPattern; typesRegistry = mockAggTypesRegistry(); }); diff --git a/src/plugins/data/common/search/aggs/agg_type.test.ts b/src/plugins/data/common/search/aggs/agg_type.test.ts index bf1136159dfe8..16a5586858ab9 100644 --- a/src/plugins/data/common/search/aggs/agg_type.test.ts +++ b/src/plugins/data/common/search/aggs/agg_type.test.ts @@ -147,6 +147,9 @@ describe('AggType Class', () => { }, }, }, + aggConfigs: { + indexPattern: { getFormatterForField: () => ({ toJSON: () => ({ id: 'format' }) }) }, + }, } as unknown) as IAggConfig; const aggType = new AggType({ name: 'name', diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 2ee604c1bf25d..1e3839038b0f7 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -271,7 +271,9 @@ export class AggType< this.getSerializedFormat = config.getSerializedFormat || ((agg: TAggConfig) => { - return agg.params.field ? agg.params.field.format.toJSON() : {}; + return agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}; }); this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {}); diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts index 1a7deafb548ae..7c09d2e64e8b7 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -140,7 +140,7 @@ export const buildOtherBucketAgg = ( const bucketAggs = aggConfigs.aggs.filter((agg) => agg.type.type === AggGroupNames.Buckets); const index = bucketAggs.findIndex((agg) => agg.id === aggWithOtherBucket.id); const aggs = aggConfigs.toDsl(); - const indexPattern = aggWithOtherBucket.params.field.indexPattern; + const indexPattern = aggWithOtherBucket.aggConfigs.indexPattern; // create filters aggregation const filterAgg = aggConfigs.createAggConfig( @@ -211,7 +211,7 @@ export const buildOtherBucketAgg = ( filters.push( buildExistsFilter( aggWithOtherBucket.params.field, - aggWithOtherBucket.params.field.indexPattern + aggWithOtherBucket.aggConfigs.indexPattern ) ); } @@ -264,7 +264,7 @@ export const mergeOtherBucketAggResponse = ( const phraseFilter = buildPhrasesFilter( otherAgg.params.field, requestFilterTerms, - otherAgg.params.field.indexPattern + otherAgg.aggConfigs.indexPattern ); phraseFilter.meta.negate = true; bucket.filters = [phraseFilter]; @@ -276,7 +276,7 @@ export const mergeOtherBucketAggResponse = ( ) ) { bucket.filters.push( - buildExistsFilter(otherAgg.params.field, otherAgg.params.field.indexPattern) + buildExistsFilter(otherAgg.params.field, otherAgg.aggConfigs.indexPattern) ); } aggResultBuckets.push(bucket); diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts index b57d530ef40e8..dc1d0ec0a152f 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/histogram.test.ts @@ -40,6 +40,7 @@ describe('AggConfig Filters', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, getConfig), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts index 30af970f55aa9..b53ae44c05075 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/range.test.ts @@ -30,7 +30,6 @@ describe('AggConfig Filters', () => { const getAggConfigs = () => { const field = { name: 'bytes', - format: new BytesFormat({}, getConfig), }; const indexPattern = { @@ -40,6 +39,7 @@ describe('AggConfig Filters', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, getConfig), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts index 95de19b96abd4..ccd1cf6e358b4 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/terms.ts @@ -27,7 +27,7 @@ import { export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, params: any) => { const field = aggConfig.params.field; - const indexPattern = field.indexPattern; + const indexPattern = aggConfig.aggConfigs.indexPattern; if (key === '__other__') { const terms = params.terms; diff --git a/src/plugins/data/common/search/aggs/buckets/date_range.ts b/src/plugins/data/common/search/aggs/buckets/date_range.ts index eda35a77afa5f..f9a3acb990fbf 100644 --- a/src/plugins/data/common/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/common/search/aggs/buckets/date_range.ts @@ -58,7 +58,9 @@ export const getDateRangeBucketAgg = ({ getSerializedFormat(agg) { return { id: 'date_range', - params: agg.params.field ? agg.params.field.format.toJSON() : {}, + params: agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}, }; }, makeLabel(aggConfig) { diff --git a/src/plugins/data/common/search/aggs/buckets/ip_range.ts b/src/plugins/data/common/search/aggs/buckets/ip_range.ts index 46e0b62d0f8d7..d0a6174b011fc 100644 --- a/src/plugins/data/common/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/common/search/aggs/buckets/ip_range.ts @@ -59,7 +59,9 @@ export const getIpRangeBucketAgg = () => getSerializedFormat(agg) { return { id: 'ip_range', - params: agg.params.field ? agg.params.field.format.toJSON() : {}, + params: agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : {}, }; }, makeLabel(aggConfig) { diff --git a/src/plugins/data/common/search/aggs/buckets/range.test.ts b/src/plugins/data/common/search/aggs/buckets/range.test.ts index b23b03db6a9ec..b8241e04ea1ee 100644 --- a/src/plugins/data/common/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/range.test.ts @@ -27,12 +27,6 @@ describe('Range Agg', () => { const getAggConfigs = () => { const field = { name: 'bytes', - format: new NumberFormat( - { - pattern: '0,0.[000] b', - }, - getConfig - ), }; const indexPattern = { @@ -42,6 +36,13 @@ describe('Range Agg', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => + new NumberFormat( + { + pattern: '0,0.[000] b', + }, + getConfig + ), } as any; return new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/buckets/range.ts b/src/plugins/data/common/search/aggs/buckets/range.ts index 91a357b635950..169b234845274 100644 --- a/src/plugins/data/common/search/aggs/buckets/range.ts +++ b/src/plugins/data/common/search/aggs/buckets/range.ts @@ -78,7 +78,9 @@ export const getRangeBucketAgg = ({ getFieldFormatsStart }: RangeBucketAggDepend return key; }, getSerializedFormat(agg) { - const format = agg.params.field ? agg.params.field.format.toJSON() : {}; + const format = agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : { id: undefined, params: undefined }; return { id: 'range', params: { diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index 5c8483cf21369..1363d38748c8b 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -82,7 +82,9 @@ export const getTermsBucketAgg = () => return agg.getFieldDisplayName() + ': ' + params.order.text; }, getSerializedFormat(agg) { - const format = agg.params.field ? agg.params.field.format.toJSON() : {}; + const format = agg.params.field + ? agg.aggConfigs.indexPattern.getFormatterForField(agg.params.field).toJSON() + : { id: undefined, params: undefined }; return { id: 'terms', params: { diff --git a/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts index c6bba56f73ec7..4815ab0ac56dc 100644 --- a/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/parent_pipeline.test.ts @@ -66,9 +66,6 @@ describe('parent pipeline aggs', function () { ) => { const field = { name: 'field', - format: { - toJSON: () => ({ id: 'bytes' }), - }, }; const indexPattern = { id: '1234', @@ -77,6 +74,9 @@ describe('parent pipeline aggs', function () { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => ({ + toJSON: () => ({ id: 'bytes' }), + }), } as any; const aggConfigs = new AggConfigs( diff --git a/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts index a157d225c839c..32737f7b7237d 100644 --- a/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/sibling_pipeline.test.ts @@ -72,6 +72,9 @@ describe('sibling pipeline aggs', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => ({ + toJSON: () => ({ id: 'bytes' }), + }), } as any; const aggConfigs = new AggConfigs( diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index a3b9b0b344823..2ad20c3807819 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -30,11 +30,7 @@ import { ValueClickContext } from '../../../../embeddable/public'; const mockField = { name: 'bytes', - indexPattern: { - id: 'logstash-*', - }, filterable: true, - format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), }; describe('createFiltersFromValueClick', () => { @@ -81,6 +77,8 @@ describe('createFiltersFromValueClick', () => { getByName: () => mockField, filter: () => [mockField], }, + getFormatterForField: () => + new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), }), } as unknown) as IndexPatternsContract); }); diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 27b16c57ffecf..a9714a95ff338 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -262,7 +262,7 @@ export { UI_SETTINGS, TypeMeta as IndexPatternTypeMeta, AggregationRestrictions as IndexPatternAggRestrictions, - FieldList, + fieldList, } from '../common'; /* diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 0c4465ae7f4b9..b09e957944b65 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -604,46 +604,11 @@ export type FieldFormatsContentType = 'html' | 'text'; // @public (undocumented) export type FieldFormatsGetConfigFn = GetConfigFn; -// Warning: (ae-missing-release-tag) "FieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "fieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class FieldList extends Array implements IIndexPatternFieldList { - // Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "OnNotification" needs to be exported by the entry point index.d.ts - constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean, onNotification?: OnNotification); - // (undocumented) - readonly add: (field: FieldSpec) => void; - // (undocumented) - readonly getAll: () => IndexPatternField[]; - // (undocumented) - readonly getByName: (name: IndexPatternField['name']) => IndexPatternField | undefined; - // (undocumented) - readonly getByType: (type: IndexPatternField['type']) => any[]; - // (undocumented) - readonly remove: (field: IFieldType) => void; - // (undocumented) - readonly removeAll: () => void; - // (undocumented) - readonly replaceAll: (specs: FieldSpec[]) => void; - // (undocumented) - readonly toSpec: () => { - count: number; - script: string | undefined; - lang: string | undefined; - conflictDescriptions: Record | undefined; - name: string; - type: string; - esTypes: string[] | undefined; - scripted: boolean; - searchable: boolean; - aggregatable: boolean; - readFromDocValues: boolean; - subType: import("../types").IFieldSubType | undefined; - format: any; - }[]; - // (undocumented) - readonly update: (field: FieldSpec) => void; -} +export const fieldList: (specs?: FieldSpec[], shortDotsEnable?: boolean) => IIndexPatternFieldList; // @public (undocumented) export interface FieldMappingSpec { @@ -868,7 +833,9 @@ export interface IFieldType { // (undocumented) subType?: IFieldSubType; // (undocumented) - toSpec?: () => FieldSpec; + toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; // (undocumented) type: string; // (undocumented) @@ -919,6 +886,10 @@ export interface IIndexPatternFieldList extends Array { // (undocumented) replaceAll(specs: FieldSpec[]): void; // (undocumented) + toSpec(options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): FieldSpec[]; + // (undocumented) update(field: FieldSpec): void; } @@ -1100,7 +1071,7 @@ export interface IndexPatternAttributes { // // @public (undocumented) export class IndexPatternField implements IFieldType { - constructor(indexPattern: IndexPattern, spec: FieldSpec, displayName: string, onNotification: OnNotification); + constructor(spec: FieldSpec, displayName: string); // (undocumented) get aggregatable(): boolean; // (undocumented) @@ -1116,10 +1087,6 @@ export class IndexPatternField implements IFieldType { // (undocumented) get filterable(): boolean; // (undocumented) - get format(): FieldFormat; - // (undocumented) - readonly indexPattern: IndexPattern; - // (undocumented) get lang(): string | undefined; set lang(lang: string | undefined); // (undocumented) @@ -1155,7 +1122,9 @@ export class IndexPatternField implements IFieldType { subType: import("../types").IFieldSubType | undefined; }; // (undocumented) - toSpec(): { + toSpec({ getFormatterForField, }?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }): { count: number; script: string | undefined; lang: string | undefined; @@ -1168,7 +1137,10 @@ export class IndexPatternField implements IFieldType { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - format: any; + format: { + id: any; + params: any; + } | undefined; }; // (undocumented) get type(): string; diff --git a/src/plugins/data/public/search/expressions/create_filter.test.ts b/src/plugins/data/public/search/expressions/create_filter.test.ts index 7968c80628531..7cc336a1c20e9 100644 --- a/src/plugins/data/public/search/expressions/create_filter.test.ts +++ b/src/plugins/data/public/search/expressions/create_filter.test.ts @@ -52,6 +52,7 @@ describe('createFilter', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => new BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), } as any; return new AggConfigs( diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 9f114f2132009..9497a41b45ff9 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -565,7 +565,9 @@ export interface IFieldType { // Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts // // (undocumented) - toSpec?: () => FieldSpec; + toSpec?: (options?: { + getFormatterForField?: IndexPattern['getFormatterForField']; + }) => FieldSpec; // (undocumented) type: string; // (undocumented) @@ -1063,6 +1065,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // Warnings were encountered during analysis: // +// src/plugins/data/common/index_patterns/fields/types.ts:41:25 - (ae-forgotten-export) The symbol "IndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 6d1238e02c7fb..b03b37da40908 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -62,7 +62,6 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals ); const field = new IndexPatternField( - indexPattern, { name: 'bytes', type: 'number', @@ -73,8 +72,7 @@ function getComponent(selected = false, showDetails = false, useShortDots = fals aggregatable: true, readFromDocValues: true, }, - 'bytes', - () => {} + 'bytes' ); const props = { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 1f27766a1756d..361c0707fef6b 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -111,8 +111,8 @@ export function DiscoverSidebar({ ); const getDetailsByField = useCallback( - (ipField: IndexPatternField) => getDetails(ipField, hits, columns), - [hits, columns] + (ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern), + [hits, columns, selectedIndexPattern] ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js index e055d644e1f91..0ee279ac5b727 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.js @@ -20,9 +20,9 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -function getFieldValues(hits, field) { +function getFieldValues(hits, field, indexPattern) { const name = field.name; - const flattenHit = field.indexPattern.flattenHit; + const flattenHit = indexPattern.flattenHit; return _.map(hits, function (hit) { return flattenHit(hit)[name]; }); @@ -49,7 +49,7 @@ function getFieldValueCounts(params) { }; } - const allValues = getFieldValues(params.hits, params.field); + const allValues = getFieldValues(params.hits, params.field, params.indexPattern); let counts; const missing = _countMissing(allValues); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts index 87401818c4907..8746883a5d968 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.test.ts @@ -148,7 +148,8 @@ describe('fieldCalculator', function () { it('Should return an array of values for _source fields', function () { const extensions = fieldCalculator.getFieldValues( hits, - indexPattern.fields.getByName('extension') + indexPattern.fields.getByName('extension'), + indexPattern ); expect(extensions).toBeInstanceOf(Array); expect( @@ -160,7 +161,11 @@ describe('fieldCalculator', function () { }); it('Should return an array of values for core meta fields', function () { - const types = fieldCalculator.getFieldValues(hits, indexPattern.fields.getByName('_type')); + const types = fieldCalculator.getFieldValues( + hits, + indexPattern.fields.getByName('_type'), + indexPattern + ); expect(types).toBeInstanceOf(Array); expect( _.filter(types, function (v) { @@ -172,12 +177,13 @@ describe('fieldCalculator', function () { }); describe('getFieldValueCounts', function () { - let params: { hits: any; field: any; count: number }; + let params: { hits: any; field: any; count: number; indexPattern: IndexPattern }; beforeEach(function () { params = { hits: _.cloneDeep(realHits), field: indexPattern.fields.getByName('extension'), count: 3, + indexPattern, }; }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts index 41d3393672474..13051f88c9591 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts @@ -19,17 +19,19 @@ // @ts-ignore import { fieldCalculator } from './field_calculator'; -import { IndexPatternField } from '../../../../../../data/public'; +import { IndexPattern, IndexPatternField } from '../../../../../../data/public'; export function getDetails( field: IndexPatternField, hits: Array>, - columns: string[] + columns: string[], + indexPattern: IndexPattern ) { const details = { ...fieldCalculator.getFieldValueCounts({ hits, field, + indexPattern, count: 5, grouped: false, }), @@ -37,7 +39,7 @@ export function getDetails( }; if (details.buckets) { for (const bucket of details.buckets) { - bucket.display = field.format.convert(bucket.value); + bucket.display = indexPattern.getFormatterForField(field).convert(bucket.value); } } return details; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts index 00e00aa8e2991..c96a8f5ce17b9 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_index_pattern_field_list.ts @@ -31,6 +31,7 @@ export function getIndexPatternFieldList( difference(fieldNamesInDocs, fieldNamesInIndexPattern).forEach((unknownFieldName) => { unknownTypes.push({ + displayName: String(unknownFieldName), name: String(unknownFieldName), type: 'unknown', } as IndexPatternField); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index 47cabc4df662f..45253f6ad27c0 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -15,13 +15,10 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` "displayName": "Elastic", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "Elastic", "searchable": true, - "type": "name", + "type": "string", }, ] } @@ -44,9 +41,6 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` "displayName": "timestamp", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "timestamp", "type": "date", @@ -72,21 +66,15 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "displayName": "Elastic", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "Elastic", "searchable": true, - "type": "name", + "type": "string", }, Object { "displayName": "timestamp", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "timestamp", "type": "date", @@ -95,9 +83,6 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "displayName": "conflictingField", "excluded": false, "format": undefined, - "indexPattern": Object { - "getNonScriptedFields": [Function], - }, "info": Array [], "name": "conflictingField", "type": "conflict", diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx index 411bbe23e4761..319b9b2b3fce2 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { IndexPatternField, IIndexPattern, IndexPattern } from 'src/plugins/data/public'; +import { IndexPatternField, IIndexPattern } from 'src/plugins/data/public'; import { IndexedFieldsTable } from './indexed_fields_table'; jest.mock('@elastic/eui', () => ({ @@ -47,10 +47,8 @@ const indexPattern = ({ const mockFieldToIndexPatternField = (spec: Record) => { return new IndexPatternField( - indexPattern as IndexPattern, (spec as unknown) as IndexPatternField['spec'], - spec.displayName as string, - () => {} + spec.displayName as string ); }; @@ -59,7 +57,7 @@ const fields = [ name: 'Elastic', displayName: 'Elastic', searchable: true, - type: 'name', + type: 'string', }, { name: 'timestamp', displayName: 'timestamp', type: 'date' }, { name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 90f81a88b3da0..23977aac7fa7a 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -77,7 +77,6 @@ export class IndexedFieldsTable extends Component< return { ...field.spec, displayName: field.displayName, - indexPattern: field.indexPattern, format: getFieldFormat(indexPattern, field.name), excluded: fieldWildcardMatch ? fieldWildcardMatch(field.name) : false, info: helpers.getFieldInfo && helpers.getFieldInfo(indexPattern, field), diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx index 6c9d6db8de130..3bc9cd34f2984 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -176,7 +176,7 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { indexedFieldTypeFilter={indexedFieldTypeFilter} helpers={{ redirectToRoute: (field: IndexPatternField) => { - history.push(getPath(field)); + history.push(getPath(field, indexPattern)); }, getFieldInfo: indexPatternManagementStart.list.getFieldInfo, }} @@ -195,7 +195,7 @@ export function Tabs({ indexPattern, fields, history, location }: TabsProps) { scriptedFieldLanguageFilter={scriptedFieldLanguageFilter} helpers={{ redirectToRoute: (field: IndexPatternField) => { - history.push(getPath(field)); + history.push(getPath(field, indexPattern)); }, }} onRemoveField={refreshFilters} diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts index b422de93de7a9..91c5cc1afdb49 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/tabs/utils.ts @@ -116,8 +116,8 @@ export function getTabs( return tabs; } -export function getPath(field: IndexPatternField) { - return `/patterns/${field.indexPattern?.id}/field/${field.name}`; +export function getPath(field: IndexPatternField, indexPattern: IndexPattern) { + return `/patterns/${indexPattern?.id}/field/${field.name}`; } const allTypesDropDown = i18n.translate( diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx index 96d3fc549ece0..b0385a61a72ac 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.test.tsx @@ -138,12 +138,12 @@ describe('FieldEditor', () => { name: 'test', script: 'doc.test.value', }; - fieldList.push(testField as IndexPatternField); + fieldList.push((testField as unknown) as IndexPatternField); indexPattern.fields.getByName = (name) => { const flds = { [testField.name]: testField, }; - return flds[name] as IndexPatternField; + return (flds[name] as unknown) as IndexPatternField; }; const component = createComponentWithContext( @@ -173,7 +173,7 @@ describe('FieldEditor', () => { const flds = { [testField.name]: testField, }; - return flds[name] as IndexPatternField; + return (flds[name] as unknown) as IndexPatternField; }; const component = createComponentWithContext( diff --git a/src/plugins/input_control_vis/public/control/control.ts b/src/plugins/input_control_vis/public/control/control.ts index 91e8f1b26164b..da2dc7bab7cf7 100644 --- a/src/plugins/input_control_vis/public/control/control.ts +++ b/src/plugins/input_control_vis/public/control/control.ts @@ -81,9 +81,10 @@ export abstract class Control { abstract destroy(): void; format = (value: any) => { + const indexPattern = this.filterManager.getIndexPattern(); const field = this.filterManager.getField(); - if (field?.format?.convert) { - return field.format.convert(value); + if (field) { + return indexPattern.getFormatterForField(field).convert(value); } return value; diff --git a/src/test_utils/public/stub_index_pattern.js b/src/test_utils/public/stub_index_pattern.js index f7b65930b683d..3289610a062b0 100644 --- a/src/test_utils/public/stub_index_pattern.js +++ b/src/test_utils/public/stub_index_pattern.js @@ -22,7 +22,7 @@ import sinon from 'sinon'; // because it is one of the few places that we need to access the IndexPattern class itself, rather // than just the type. Doing this as a temporary measure; it will be left behind when migrating to NP. -import { IndexPattern, indexPatterns, KBN_FIELD_TYPES, FieldList } from '../../plugins/data/public'; +import { IndexPattern, indexPatterns, KBN_FIELD_TYPES, fieldList } from '../../plugins/data/public'; import { setFieldFormats } from '../../plugins/data/public/services'; @@ -64,7 +64,7 @@ export default function StubIndexPattern(pattern, getConfig, timeField, fields, }); this._reindexFields = function () { - this.fields = new FieldList(this, this.fields || fields, false); + this.fields = fieldList(this.fields || fields, false); }; this.stubSetFieldFormat = function (fieldName, id, params) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index 56b830e9ff098..d51ca46fd98ff 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -281,7 +281,7 @@ export class AbstractESSource extends AbstractVectorSource { return null; } - return fieldFromIndexPattern.format.getConverterFor('text'); + return indexPattern.getFormatterForField(fieldFromIndexPattern).getConverterFor('text'); } async loadStylePropsMeta( diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts index 516ad25f933f6..348cd5e552258 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts @@ -10,8 +10,8 @@ import { IField } from '../fields/field'; import { esFilters, Filter, - IFieldType, IndexPattern, + IndexPatternField, } from '../../../../../../src/plugins/data/public'; export class ESTooltipProperty implements ITooltipProperty { @@ -37,7 +37,7 @@ export class ESTooltipProperty implements ITooltipProperty { return this._tooltipProperty.getRawValue(); } - _getIndexPatternField(): IFieldType | undefined { + _getIndexPatternField(): IndexPatternField | undefined { return this._indexPattern.fields.getByName(this._field.getRootName()); } @@ -56,10 +56,11 @@ export class ESTooltipProperty implements ITooltipProperty { } } - const htmlConverter = indexPatternField.format.getConverterFor('html'); + const formatter = this._indexPattern.getFormatterForField(indexPatternField); + const htmlConverter = formatter.getConverterFor('html'); return htmlConverter ? htmlConverter(this.getRawValue()) - : indexPatternField.format.convert(this.getRawValue()); + : formatter.convert(this.getRawValue()); } isFilterable(): boolean { diff --git a/x-pack/plugins/ml/public/application/services/field_format_service.ts b/x-pack/plugins/ml/public/application/services/field_format_service.ts index 1a5d22e7320a5..b66dac15b1364 100644 --- a/x-pack/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/plugins/ml/public/application/services/field_format_service.ts @@ -77,7 +77,7 @@ class FieldFormatService { const fieldList = fullIndexPattern.fields; const field = fieldList.getByName(fieldName); if (field !== undefined) { - fieldFormat = field.format; + fieldFormat = fullIndexPattern.getFormatterForField(field); } } @@ -104,7 +104,9 @@ class FieldFormatService { if (dtr.field_name !== undefined && esAgg !== 'cardinality') { const field = fieldList.getByName(dtr.field_name); if (field !== undefined) { - formatsByDetector[dtr.detector_index!] = field.format; + formatsByDetector[dtr.detector_index!] = indexPatternData.getFormatterForField( + field + ); } } }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 0d98a0f2f26ff..073cb46d3949a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -140,7 +140,11 @@ describe('helpers', () => { filterManager: mockFilterManager, query: mockQueryBarWithFilters.query, savedId: mockQueryBarWithFilters.saved_id, - indexPatterns: { fields: [{ name: 'test name', type: 'test type' }], title: 'test title' }, + indexPatterns: { + fields: [{ name: 'event.category', type: 'test type' }], + title: 'test title', + getFormatterForField: () => ({ convert: (val: unknown) => val }), + }, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); From 9519fcd4a22d48c1ae8c01784d73acd4e67f9e19 Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Wed, 2 Sep 2020 15:08:43 -0600 Subject: [PATCH 15/43] Fix issue where defaultAppId redirect could fire outside home app (#76415) --- src/plugins/home/public/application/application.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/home/public/application/application.tsx b/src/plugins/home/public/application/application.tsx index 5d71bf8651d88..a4a158bc7dbde 100644 --- a/src/plugins/home/public/application/application.tsx +++ b/src/plugins/home/public/application/application.tsx @@ -47,6 +47,13 @@ export const renderApp = async ( chrome.setBreadcrumbs([{ text: homeTitle }]); + // dispatch synthetic hash change event to update hash history objects + // this is necessary because hash updates triggered by using popState won't trigger this event naturally. + // This must be called before the app is mounted to avoid call this after the redirect to default app logic kicks in + const unlisten = history.listen((location) => { + window.dispatchEvent(new HashChangeEvent('hashchange')); + }); + render( @@ -54,12 +61,6 @@ export const renderApp = async ( element ); - // dispatch synthetic hash change event to update hash history objects - // this is necessary because hash updates triggered by using popState won't trigger this event naturally. - const unlisten = history.listen(() => { - window.dispatchEvent(new HashChangeEvent('hashchange')); - }); - return () => { unmountComponentAtNode(element); unlisten(); From 1ffe24955cc72e2453b6f643a0f38876fa394ad3 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 2 Sep 2020 14:53:58 -0700 Subject: [PATCH 16/43] Split up and clarify Enterprise Search codeowners (#76580) @elastic/enterprise-search-frontend is a shared team of both app-search-frontend and workplace-search-frontend, but combining it into one group means only 1 reviewer is required (as opposed to 1 per team). --- .github/CODEOWNERS | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1d0f6fc50ee9b..b4563dd1f9a9c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -209,8 +209,19 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib /x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services # Enterprise Search -/x-pack/plugins/enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend -/x-pack/test/functional_enterprise_search/ @elastic/app-search-frontend @elastic/workplace-search-frontend +# Shared +/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search @elastic/workplace-search-frontend # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui From 46aef4b1e0594e41c51a6b4ed0ba069872c5bc33 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 2 Sep 2020 15:18:37 -0700 Subject: [PATCH 17/43] skip failing suite (#76581) --- .../reporting_api_integration/reporting_and_security/usage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts index feda5c1386e98..49db8696c1134 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts @@ -21,7 +21,8 @@ export default function ({ getService }: FtrProviderContext) { const reportingAPI = getService('reportingAPI'); const usageAPI = getService('usageAPI'); - describe('Usage', () => { + // FAILING: https://github.com/elastic/kibana/issues/76581 + describe.skip('Usage', () => { before(async () => { await esArchiver.load(OSS_KIBANA_ARCHIVE_PATH); await esArchiver.load(OSS_DATA_ARCHIVE_PATH); From ee2ceef9bb904e18487441fe5f0853f2f9a3967a Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 2 Sep 2020 17:20:14 -0500 Subject: [PATCH 18/43] Index Pattern class - remove toJSON and toString (#76246) * remove toJSON * remove toJSON, toString * fix ml url * Update actions_panel.tsx * fix url Co-authored-by: Elastic Machine --- ...ana-plugin-plugins-data-public.indexpattern.md | 2 -- ...gin-plugins-data-public.indexpattern.tojson.md | 15 --------------- ...n-plugins-data-public.indexpattern.tostring.md | 15 --------------- .../index_patterns/index_pattern.test.ts | 2 -- .../index_patterns/index_pattern.ts | 8 -------- src/plugins/data/public/public.api.md | 4 ---- .../components/actions_panel/actions_panel.tsx | 6 ++++-- 7 files changed, 4 insertions(+), 48 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 37db063e284ec..4c53af3f8970e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -61,7 +61,5 @@ export declare class IndexPattern implements IIndexPattern | [refreshFields()](./kibana-plugin-plugins-data-public.indexpattern.refreshfields.md) | | | | [removeScriptedField(fieldName)](./kibana-plugin-plugins-data-public.indexpattern.removescriptedfield.md) | | | | [save(saveAttempts)](./kibana-plugin-plugins-data-public.indexpattern.save.md) | | | -| [toJSON()](./kibana-plugin-plugins-data-public.indexpattern.tojson.md) | | | | [toSpec()](./kibana-plugin-plugins-data-public.indexpattern.tospec.md) | | | -| [toString()](./kibana-plugin-plugins-data-public.indexpattern.tostring.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md deleted file mode 100644 index 0ae04bb424d44..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tojson.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [toJSON](./kibana-plugin-plugins-data-public.indexpattern.tojson.md) - -## IndexPattern.toJSON() method - -Signature: - -```typescript -toJSON(): string | undefined; -``` -Returns: - -`string | undefined` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md deleted file mode 100644 index a10b549a7b9eb..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.tostring.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [toString](./kibana-plugin-plugins-data-public.indexpattern.tostring.md) - -## IndexPattern.toString() method - -Signature: - -```typescript -toString(): string; -``` -Returns: - -`string` - diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index 2350cbb67683e..f037a71b508a2 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -151,8 +151,6 @@ describe('IndexPattern', () => { expect(indexPattern).toHaveProperty('getNonScriptedFields'); expect(indexPattern).toHaveProperty('addScriptedField'); expect(indexPattern).toHaveProperty('removeScriptedField'); - expect(indexPattern).toHaveProperty('toString'); - expect(indexPattern).toHaveProperty('toJSON'); expect(indexPattern).toHaveProperty('save'); // properties diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index c82bf7d759328..2feeb5441ab83 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -650,12 +650,4 @@ export class IndexPattern implements IIndexPattern { }); }); } - - toJSON() { - return this.id; - } - - toString() { - return '' + this.toJSON(); - } } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index b09e957944b65..e72fea160cc28 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1022,12 +1022,8 @@ export class IndexPattern implements IIndexPattern { // (undocumented) title: string; // (undocumented) - toJSON(): string | undefined; - // (undocumented) toSpec(): IndexPatternSpec; // (undocumented) - toString(): string; - // (undocumented) type: string | undefined; // (undocumented) typeMeta?: IndexPatternTypeMeta; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx index 16d9e0c5418fa..1f2c97b128e3f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx @@ -13,6 +13,7 @@ import { EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { CreateJobLinkCard } from '../../../../components/create_job_link_card'; import { DataRecognizer } from '../../../../components/data_recognizer'; +import { getBasePath } from '../../../../util/dependency_cache'; interface Props { indexPattern: IndexPattern; @@ -20,6 +21,7 @@ interface Props { export const ActionsPanel: FC = ({ indexPattern }) => { const [recognizerResultsCount, setRecognizerResultsCount] = useState(0); + const basePath = getBasePath(); const recognizerResults = { count: 0, @@ -31,7 +33,7 @@ export const ActionsPanel: FC = ({ indexPattern }) => { function openAdvancedJobWizard() { // TODO - pass the search string to the advanced job page as well as the index pattern // (add in with new advanced job wizard?) - window.open(`#/jobs/new_job/advanced?index=${indexPattern}`, '_self'); + window.open(`${basePath.get()}/app/ml/jobs/new_job/advanced?index=${indexPattern.id}`, '_self'); } // Note we use display:none for the DataRecognizer section as it needs to be @@ -86,7 +88,7 @@ export const ActionsPanel: FC = ({ indexPattern }) => { 'Use the full range of options to create a job for more advanced use cases', })} onClick={openAdvancedJobWizard} - href={`#/jobs/new_job/advanced?index=${indexPattern}`} + href={`${basePath.get()}/app/ml/jobs/new_job/advanced?index=${indexPattern.id}`} data-test-subj="mlDataVisualizerCreateAdvancedJobCard" />
From aab8d3c7ddf97306f00c219f6ab30d6e4cec2618 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 2 Sep 2020 20:07:40 -0400 Subject: [PATCH 19/43] [Security Solution][Exceptions] - Updates enum schema and tests (#76544) ## Summary Mistmatch caught between the io-ts type and the corresponding typescript enum. Currently, io-ts does not have support for enums - as a workaround I had made a matching typescript enum type. Tests were added to try to ensure the two stayed in sync, but didn't do a straight up comparison of the two. Updated the tests to now error if the keys do not match. --- .../common/schemas/common/schemas.test.ts | 66 +++---------------- .../lists/common/schemas/common/schemas.ts | 7 -- x-pack/plugins/lists/common/shared_exports.ts | 1 - .../common/shared_imports.ts | 1 - 4 files changed, 10 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.test.ts b/x-pack/plugins/lists/common/schemas/common/schemas.test.ts index fad8ecc86277b..ec3871b673888 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.test.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.test.ts @@ -16,6 +16,8 @@ import { EsDataTypeRangeTerm, EsDataTypeSingle, EsDataTypeUnion, + ExceptionListTypeEnum, + OperatorEnum, Type, esDataTypeGeoPoint, esDataTypeGeoPointRange, @@ -25,60 +27,10 @@ import { esDataTypeUnion, exceptionListType, operator, - operator_type as operatorType, type, } from './schemas'; describe('Common schemas', () => { - describe('operatorType', () => { - test('it should validate for "match"', () => { - const payload = 'match'; - const decoded = operatorType.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate for "match_any"', () => { - const payload = 'match_any'; - const decoded = operatorType.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate for "list"', () => { - const payload = 'list'; - const decoded = operatorType.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate for "exists"', () => { - const payload = 'exists'; - const decoded = operatorType.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should contain 4 keys', () => { - // Might seem like a weird test, but its meant to - // ensure that if operatorType is updated, you - // also update the OperatorTypeEnum, a workaround - // for io-ts not yet supporting enums - // https://github.com/gcanti/io-ts/issues/67 - const keys = Object.keys(operatorType.keys); - - expect(keys.length).toEqual(4); - }); - }); - describe('operator', () => { test('it should validate for "included"', () => { const payload = 'included'; @@ -98,15 +50,16 @@ describe('Common schemas', () => { expect(message.schema).toEqual(payload); }); - test('it should contain 2 keys', () => { + test('it should contain same amount of keys as enum', () => { // Might seem like a weird test, but its meant to // ensure that if operator is updated, you // also update the operatorEnum, a workaround // for io-ts not yet supporting enums // https://github.com/gcanti/io-ts/issues/67 - const keys = Object.keys(operator.keys); + const keys = Object.keys(operator.keys).sort().join(',').toLowerCase(); + const enumKeys = Object.keys(OperatorEnum).sort().join(',').toLowerCase(); - expect(keys.length).toEqual(2); + expect(keys).toEqual(enumKeys); }); }); @@ -129,15 +82,16 @@ describe('Common schemas', () => { expect(message.schema).toEqual(payload); }); - test('it should contain 2 keys', () => { + test('it should contain same amount of keys as enum', () => { // Might seem like a weird test, but its meant to // ensure that if exceptionListType is updated, you // also update the ExceptionListTypeEnum, a workaround // for io-ts not yet supporting enums // https://github.com/gcanti/io-ts/issues/67 - const keys = Object.keys(exceptionListType.keys); + const keys = Object.keys(exceptionListType.keys).sort().join(',').toLowerCase(); + const enumKeys = Object.keys(ExceptionListTypeEnum).sort().join(',').toLowerCase(); - expect(keys.length).toEqual(2); + expect(keys).toEqual(enumKeys); }); }); diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index 1556ef5a5dab9..37da5fbcd1a1b 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -282,13 +282,6 @@ export enum OperatorEnum { EXCLUDED = 'excluded', } -export const operator_type = t.keyof({ - exists: null, - list: null, - match: null, - match_any: null, -}); -export type OperatorType = t.TypeOf; export enum OperatorTypeEnum { NESTED = 'nested', MATCH = 'match', diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 1f6c65919b063..361837bdef229 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -25,7 +25,6 @@ export { NamespaceType, Operator, OperatorEnum, - OperatorType, OperatorTypeEnum, ExceptionListTypeEnum, comment, diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index e28d1969b3976..564254b6a7596 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -25,7 +25,6 @@ export { NamespaceType, Operator, OperatorEnum, - OperatorType, OperatorTypeEnum, ExceptionListTypeEnum, exceptionListItemSchema, From 2f017b0e85c5e2e82d344a5fe8d8408a2faec502 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Wed, 2 Sep 2020 21:48:41 -0600 Subject: [PATCH 20/43] [Security Solution][Detection Engine] Remove RuleTypeSchema in favor of Type for TypeScript (#76586) ## Summary Removes RuleTypeSchema in favor of Type for TypeScript. Does break out one function called `parseScheduleDates` into its own file to remove a circular ref issue. --- .../detection_engine/parse_schedule_dates.ts | 20 +++++++++ .../schemas/common/schemas.ts | 2 +- .../common/detection_engine/types.ts | 10 ----- .../common/detection_engine/utils.ts | 19 +-------- .../common/machine_learning/helpers.ts | 4 +- .../timeline_actions/alert_context_menu.tsx | 5 +-- .../rules/description_step/helpers.tsx | 5 +-- .../rules/description_step/index.tsx | 4 +- .../rules/select_rule_type/index.tsx | 6 +-- .../detection_engine/rules/types.ts | 6 +-- .../detection_engine/rules/create/helpers.ts | 6 +-- .../pages/detection_engine/rules/helpers.tsx | 41 ++++++++++--------- .../pages/detection_engine/rules/types.ts | 7 ++-- .../rules_notification_alert_type.ts | 2 +- .../signals/signal_rule_alert_type.test.ts | 3 +- .../signals/signal_rule_alert_type.ts | 2 +- .../detection_engine/signals/utils.test.ts | 2 +- .../lib/detection_engine/signals/utils.ts | 3 +- .../server/lib/detection_engine/types.ts | 4 +- .../server/lib/machine_learning/authz.ts | 6 +-- 20 files changed, 77 insertions(+), 80 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/parse_schedule_dates.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/parse_schedule_dates.ts b/x-pack/plugins/security_solution/common/detection_engine/parse_schedule_dates.ts new file mode 100644 index 0000000000000..b21cd84684fbc --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/parse_schedule_dates.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 moment from 'moment'; +import dateMath from '@elastic/datemath'; + +export const parseScheduleDates = (time: string): moment.Moment | null => { + const isValidDateString = !isNaN(Date.parse(time)); + const isValidInput = isValidDateString || time.trim().startsWith('now'); + const formattedDate = isValidDateString + ? moment(time) + : isValidInput + ? dateMath.parse(time) + : null; + + return formattedDate ?? null; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 64f2f223a3073..a9f39d2db6080 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -14,7 +14,7 @@ import { UUID } from '../types/uuid'; import { IsoDateString } from '../types/iso_date_string'; import { PositiveIntegerGreaterThanZero } from '../types/positive_integer_greater_than_zero'; import { PositiveInteger } from '../types/positive_integer'; -import { parseScheduleDates } from '../../utils'; +import { parseScheduleDates } from '../../parse_schedule_dates'; export const author = t.array(t.string); export type Author = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/types.ts b/x-pack/plugins/security_solution/common/detection_engine/types.ts index 7c752bca49dbd..e7aab2fa5d219 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -3,18 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; - import { AlertAction } from '../../../alerts/common'; export type RuleAlertAction = Omit & { action_type_id: string; }; - -export const RuleTypeSchema = t.keyof({ - query: null, - saved_query: null, - machine_learning: null, - threshold: null, -}); -export type RuleType = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index a70258c2684b6..a72d641fbaf78 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -4,11 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import dateMath from '@elastic/datemath'; - import { EntriesArray } from '../shared_imports'; -import { RuleType } from './types'; +import { Type } from './schemas/common/schemas'; export const hasLargeValueList = (entries: EntriesArray): boolean => { const found = entries.filter(({ type }) => type === 'list'); @@ -20,16 +17,4 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { return found.length > 0; }; -export const isThresholdRule = (ruleType: RuleType) => ruleType === 'threshold'; - -export const parseScheduleDates = (time: string): moment.Moment | null => { - const isValidDateString = !isNaN(Date.parse(time)); - const isValidInput = isValidDateString || time.trim().startsWith('now'); - const formattedDate = isValidDateString - ? moment(time) - : isValidInput - ? dateMath.parse(time) - : null; - - return formattedDate ?? null; -}; +export const isThresholdRule = (ruleType: Type) => ruleType === 'threshold'; diff --git a/x-pack/plugins/security_solution/common/machine_learning/helpers.ts b/x-pack/plugins/security_solution/common/machine_learning/helpers.ts index fe3eb79a6f610..73dc30f238c7e 100644 --- a/x-pack/plugins/security_solution/common/machine_learning/helpers.ts +++ b/x-pack/plugins/security_solution/common/machine_learning/helpers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleType } from '../detection_engine/types'; +import { Type } from '../detection_engine/schemas/common/schemas'; // Based on ML Job/Datafeed States from x-pack/legacy/plugins/ml/common/constants/states.js const enabledStates = ['started', 'opened']; @@ -23,4 +23,4 @@ export const isJobFailed = (jobState: string, datafeedState: string): boolean => return failureStates.includes(jobState) || failureStates.includes(datafeedState); }; -export const isMlRule = (ruleType: RuleType) => ruleType === 'machine_learning'; +export const isMlRule = (ruleType: Type) => ruleType === 'machine_learning'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 589116c901c30..216ed0cbe264d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -17,9 +17,8 @@ import styled from 'styled-components'; import { TimelineId } from '../../../../../common/types/timeline'; import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; -import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { Status, Type } from '../../../../../common/detection_engine/schemas/common/schemas'; import { isThresholdRule } from '../../../../../common/detection_engine/utils'; -import { RuleType } from '../../../../../common/detection_engine/types'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { timelineActions } from '../../../../timelines/store/timeline'; import { EventsTd, EventsTdContent } from '../../../../timelines/components/timeline/styles'; @@ -409,7 +408,7 @@ const AlertContextMenuComponent: React.FC = ({ data: nonEcsRowData, fieldName: 'signal.rule.type', }); - const [ruleType] = ruleTypes as RuleType[]; + const [ruleType] = ruleTypes as Type[]; return !isMlRule(ruleType) && !isThresholdRule(ruleType); }, [nonEcsRowData]); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 3a0a5b04c5874..680ca78fc222e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -24,8 +24,7 @@ import styled from 'styled-components'; import { assertUnreachable } from '../../../../../common/utility_types'; import * as i18nSeverity from '../severity_mapping/translations'; import * as i18nRiskScore from '../risk_score_mapping/translations'; -import { Threshold } from '../../../../../common/detection_engine/schemas/common/schemas'; -import { RuleType } from '../../../../../common/detection_engine/types'; +import { Threshold, Type } from '../../../../../common/detection_engine/schemas/common/schemas'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; @@ -357,7 +356,7 @@ export const buildNoteDescription = (label: string, note: string): ListItems[] = return []; }; -export const buildRuleTypeDescription = (label: string, ruleType: RuleType): ListItems[] => { +export const buildRuleTypeDescription = (label: string, ruleType: Type): ListItems[] => { switch (ruleType) { case 'machine_learning': { return [ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index 00141c9a453d8..cf27fa97b1479 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -9,7 +9,6 @@ import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp'; import React, { memo, useState } from 'react'; import styled from 'styled-components'; -import { RuleType } from '../../../../../common/detection_engine/types'; import { IIndexPattern, Filter, @@ -42,6 +41,7 @@ import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/ import { buildMlJobDescription } from './ml_job_description'; import { buildActionsDescription } from './actions_description'; import { buildThrottleDescription } from './throttle_description'; +import { Type } from '../../../../../common/detection_engine/schemas/common/schemas'; const DescriptionListContainer = styled(EuiDescriptionList)` &.euiDescriptionList--column .euiDescriptionList__title { @@ -211,7 +211,7 @@ export const getDescriptionItem = ( const val: string = get(field, data); return buildNoteDescription(label, val); } else if (field === 'ruleType') { - const ruleType: RuleType = get(field, data); + const ruleType: Type = get(field, data); return buildRuleTypeDescription(label, ruleType); } else if (field === 'kibanaSiemAppUrl') { return []; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx index c6ea269e1a355..6f5f3361d2b3e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx @@ -9,11 +9,11 @@ import { EuiCard, EuiFlexGrid, EuiFlexItem, EuiFormRow, EuiIcon } from '@elastic import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { isThresholdRule } from '../../../../../common/detection_engine/utils'; -import { RuleType } from '../../../../../common/detection_engine/types'; import { FieldHook } from '../../../../shared_imports'; import { useKibana } from '../../../../common/lib/kibana'; import * as i18n from './translations'; import { MlCardDescription } from './ml_card_description'; +import { Type } from '../../../../../common/detection_engine/schemas/common/schemas'; interface SelectRuleTypeProps { describedByIds?: string[]; @@ -30,9 +30,9 @@ export const SelectRuleType: React.FC = ({ hasValidLicense = false, isMlAdmin = false, }) => { - const ruleType = field.value as RuleType; + const ruleType = field.value as Type; const setType = useCallback( - (type: RuleType) => { + (type: Type) => { field.setValue(type); }, [field] diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index a5bbc0465ccc1..166bb90113aef 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -6,7 +6,6 @@ import * as t from 'io-ts'; -import { RuleTypeSchema } from '../../../../../common/detection_engine/types'; import { author, building_block_type, @@ -16,6 +15,7 @@ import { severity_mapping, timestamp_override, threshold, + type, } from '../../../../../common/detection_engine/schemas/common/schemas'; import { listArray, @@ -44,7 +44,7 @@ export const NewRuleSchema = t.intersection([ name: t.string, risk_score: t.number, severity: t.string, - type: RuleTypeSchema, + type, }), t.partial({ actions: t.array(action), @@ -117,7 +117,7 @@ export const RuleSchema = t.intersection([ severity: t.string, severity_mapping, tags: t.array(t.string), - type: RuleTypeSchema, + type, to: t.string, threat: t.array(t.unknown), updated_at: t.string, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 434a33ac37722..f4a40b771c9fa 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -10,12 +10,12 @@ import deepmerge from 'deepmerge'; import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants'; import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; -import { RuleType } from '../../../../../../common/detection_engine/types'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { isThresholdRule } from '../../../../../../common/detection_engine/utils'; import { List } from '../../../../../../common/detection_engine/schemas/types'; import { ENDPOINT_LIST_ID } from '../../../../../shared_imports'; import { NewRule, Rule } from '../../../../containers/detection_engine/rules'; +import { Type } from '../../../../../../common/detection_engine/schemas/common/schemas'; import { AboutStepRule, @@ -68,7 +68,7 @@ const isThresholdFields = ( fields: QueryRuleFields | MlRuleFields | ThresholdRuleFields ): fields is ThresholdRuleFields => has('threshold', fields); -export const filterRuleFieldsForType = (fields: T, type: RuleType) => { +export const filterRuleFieldsForType = (fields: T, type: Type) => { if (isMlRule(type)) { const { index, queryBar, threshold, ...mlRuleFields } = fields; return mlRuleFields; @@ -119,7 +119,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep query: ruleFields.queryBar?.query?.query as string, saved_id: ruleFields.queryBar?.saved_id, ...(ruleType === 'query' && - ruleFields.queryBar?.saved_id && { type: 'saved_query' as RuleType }), + ruleFields.queryBar?.saved_id && { type: 'saved_query' as Type }), }; return { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index f9279ce639534..8178f5ae5ba1d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -10,7 +10,7 @@ import memoizeOne from 'memoize-one'; import { useLocation } from 'react-router-dom'; import { ActionVariable } from '../../../../../../triggers_actions_ui/public'; -import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; +import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { transformRuleToAlertAction } from '../../../../../common/detection_engine/transform_actions'; import { Filter } from '../../../../../../../../src/plugins/data/public'; @@ -24,7 +24,10 @@ import { ScheduleStepRule, ActionsStepRule, } from './types'; -import { SeverityMapping } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { + SeverityMapping, + Type, +} from '../../../../../common/detection_engine/schemas/common/schemas'; import { severityOptions } from '../../../components/rules/step_about_rule/data'; export interface GetStepsData { @@ -307,7 +310,7 @@ export const redirectToDetections = ( hasEncryptionKey === false || needsListsConfiguration; -const getRuleSpecificRuleParamKeys = (ruleType: RuleType) => { +const getRuleSpecificRuleParamKeys = (ruleType: Type) => { const queryRuleParams = ['index', 'filters', 'language', 'query', 'saved_id']; if (isMlRule(ruleType)) { @@ -321,7 +324,7 @@ const getRuleSpecificRuleParamKeys = (ruleType: RuleType) => { return queryRuleParams; }; -export const getActionMessageRuleParams = (ruleType: RuleType): string[] => { +export const getActionMessageRuleParams = (ruleType: Type): string[] => { const commonRuleParamsKeys = [ 'id', 'name', @@ -349,23 +352,21 @@ export const getActionMessageRuleParams = (ruleType: RuleType): string[] => { return ruleParamsKeys; }; -export const getActionMessageParams = memoizeOne( - (ruleType: RuleType | undefined): ActionVariable[] => { - if (!ruleType) { - return []; - } - const actionMessageRuleParams = getActionMessageRuleParams(ruleType); - - return [ - { name: 'state.signals_count', description: 'state.signals_count' }, - { name: '{context.results_link}', description: 'context.results_link' }, - ...actionMessageRuleParams.map((param) => { - const extendedParam = `context.rule.${param}`; - return { name: extendedParam, description: extendedParam }; - }), - ]; +export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): ActionVariable[] => { + if (!ruleType) { + return []; } -); + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + + return [ + { name: 'state.signals_count', description: 'state.signals_count' }, + { name: '{context.results_link}', description: 'context.results_link' }, + ...actionMessageRuleParams.map((param) => { + const extendedParam = `context.rule.${param}`; + return { name: extendedParam, description: extendedParam }; + }), + ]; +}); // typed as null not undefined as the initial state for this value is null. export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean => diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index e36f08703dae5..891af4b8ca80e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types'; +import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { AlertAction } from '../../../../../../alerts/common'; import { Filter } from '../../../../../../../../src/plugins/data/common'; import { FormData, FormHook } from '../../../../shared_imports'; @@ -19,6 +19,7 @@ import { RuleNameOverride, SeverityMapping, TimestampOverride, + Type, } from '../../../../../common/detection_engine/schemas/common/schemas'; import { List } from '../../../../../common/detection_engine/schemas/types'; @@ -102,7 +103,7 @@ export interface DefineStepRule extends StepRuleData { index: string[]; machineLearningJobId: string; queryBar: FieldValueQueryBar; - ruleType: RuleType; + ruleType: Type; timeline: FieldValueTimeline; threshold: FieldValueThreshold; } @@ -134,7 +135,7 @@ export interface DefineStepRuleJson { }; timeline_id?: string; timeline_title?: string; - type: RuleType; + type: Type; } export interface AboutStepRuleJson { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index 0a899562d61c2..802b9472a4487 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -14,7 +14,7 @@ import { RuleAlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; -import { parseScheduleDates } from '../../../../common/detection_engine/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; export const rulesNotificationAlertType = ({ logger, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index a7213c30eb3fb..b8311182f3ca8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -17,7 +17,7 @@ import { getExceptions, sortExceptionItems, } from './utils'; -import { parseScheduleDates } from '../../../../common/detection_engine/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { RuleExecutorOptions } from './types'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; @@ -38,6 +38,7 @@ jest.mock('../notifications/schedule_notification_actions'); jest.mock('./find_ml_signals'); jest.mock('./bulk_create_ml_signals'); jest.mock('./../../../../common/detection_engine/utils'); +jest.mock('../../../../common/detection_engine/parse_schedule_dates'); const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({ alertId: ruleAlert.id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 9d688736a9846..da17d4a1f123a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -14,7 +14,7 @@ import { SERVER_APP_ID, } from '../../../../common/constants'; import { isJobStarted, isMlRule } from '../../../../common/machine_learning/helpers'; -import { parseScheduleDates } from '../../../../common/detection_engine/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { SetupPlugins } from '../../../plugin'; import { getInputIndex } from './get_input_output_index'; import { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index a2e2fec3309c3..d053a8e1089ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -13,7 +13,7 @@ import { buildRuleMessageFactory } from './rule_messages'; import { ExceptionListClient } from '../../../../../lists/server'; import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; -import { parseScheduleDates } from '../../../../common/detection_engine/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { generateId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 92cc9be69839f..dc09c6d5386fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -14,7 +14,8 @@ import { ExceptionListItemSchema } from '../../../../../lists/common/schemas'; import { ListArrayOrUndefined } from '../../../../common/detection_engine/schemas/types/lists'; import { BulkResponse, BulkResponseErrorAggregation, isValidUnit } from './types'; import { BuildRuleMessage } from './rule_messages'; -import { hasLargeValueList, parseScheduleDates } from '../../../../common/detection_engine/utils'; +import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; +import { hasLargeValueList } from '../../../../common/detection_engine/utils'; import { MAX_EXCEPTION_LIST_SIZE } from '../../../../../lists/common/constants'; interface SortExceptionsReturn { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index 4b4f5147c9a42..cbe756064b72b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -36,10 +36,10 @@ import { RuleNameOverrideOrUndefined, SeverityMappingOrUndefined, TimestampOverrideOrUndefined, + Type, } from '../../../common/detection_engine/schemas/common/schemas'; import { LegacyCallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; -import { RuleType } from '../../../common/detection_engine/types'; import { ListArrayOrUndefined } from '../../../common/detection_engine/schemas/types'; export type PartialFilter = Partial; @@ -75,7 +75,7 @@ export interface RuleTypeParams { threshold: ThresholdOrUndefined; timestampOverride: TimestampOverrideOrUndefined; to: To; - type: RuleType; + type: Type; references: References; version: Version; exceptionsList: ListArrayOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts index 386eda5281f0c..ff565c05848f9 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/authz.ts @@ -13,12 +13,12 @@ import { SetupPlugins } from '../../plugin'; import { MINIMUM_ML_LICENSE } from '../../../common/constants'; import { hasMlAdminPermissions } from '../../../common/machine_learning/has_ml_admin_permissions'; import { isMlRule } from '../../../common/machine_learning/helpers'; -import { RuleType } from '../../../common/detection_engine/types'; import { Validation } from './validation'; import { cache } from './cache'; +import { Type } from '../../../common/detection_engine/schemas/common/schemas'; export interface MlAuthz { - validateRuleType: (type: RuleType) => Promise; + validateRuleType: (type: Type) => Promise; } /** @@ -40,7 +40,7 @@ export const buildMlAuthz = ({ request: KibanaRequest; }): MlAuthz => { const cachedValidate = cache(() => validateMlAuthz({ license, ml, request })); - const validateRuleType = async (type: RuleType): Promise => { + const validateRuleType = async (type: Type): Promise => { if (!isMlRule(type)) { return { valid: true, message: undefined }; } else { From d3416c018907f13da89d99356322d0c7b9936c0d Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 2 Sep 2020 21:39:03 -0700 Subject: [PATCH 21/43] [APM] Service maps layout enhancements (#76481) * Fixes storybook anomaly score generation and better utilizes available screen space * Closes #71770 for APM service maps by replacing breadthfirst layout with one from the cytoscape-dagre extension. Also replaces the taxi edges with cubic bezier edges. Finally, this adds the ability to drag individual nodes around the service map. * Removes unused code * removes commented line of code * - Adds ability for scripts/notice.js to check files with the .tsx file extension - Adds attribution for `applyCubicBezierStyles` * Refine comment text and MIT license url --- NOTICE.txt | 7 ++ src/dev/notice/generate_notice_from_source.ts | 2 +- x-pack/package.json | 1 + .../components/app/ServiceMap/Cytoscape.tsx | 98 ++++++++----------- .../app/ServiceMap/Popover/index.tsx | 2 + .../__stories__/Cytoscape.stories.tsx | 4 +- .../CytoscapeExampleData.stories.tsx | 25 +++-- .../generate_service_map_elements.ts | 25 +++-- .../app/ServiceMap/cytoscapeOptions.ts | 5 +- .../components/app/ServiceMap/index.tsx | 3 +- .../plugins/apm/typings/cytoscape_dagre.d.ts | 7 ++ yarn.lock | 22 +++++ 12 files changed, 113 insertions(+), 88 deletions(-) create mode 100644 x-pack/plugins/apm/typings/cytoscape_dagre.d.ts diff --git a/NOTICE.txt b/NOTICE.txt index e1552852d0349..d689abf4c4e05 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -281,6 +281,13 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +--- +This product includes code in the function applyCubicBezierStyles that was +inspired by a public Codepen, which was available under a "MIT" license. + +Copyright (c) 2020 by Guillaume (https://codepen.io/guillaumethomas/pen/xxbbBKO) +MIT License http://www.opensource.org/licenses/mit-license + --- This product includes code that is adapted from mapbox-gl-js, which is available under a "BSD-3-Clause" license. diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index 0bef5bc5f32d4..9f7eb9d9e1aa4 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -41,7 +41,7 @@ interface Options { * into the repository. */ export async function generateNoticeFromSource({ productName, directory, log }: Options) { - const globs = ['**/*.{js,less,css,ts}']; + const globs = ['**/*.{js,less,css,ts,tsx}']; const options = { cwd: directory, diff --git a/x-pack/package.json b/x-pack/package.json index 0e9aef37aa253..bf093fed30747 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -305,6 +305,7 @@ "concat-stream": "1.6.2", "content-disposition": "0.5.3", "cytoscape": "^3.10.0", + "cytoscape-dagre": "^2.2.2", "d3-array": "1.2.4", "dedent": "^0.7.0", "del": "^5.1.0", diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 1cde473aae6fa..0b00c8a8bf093 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -13,6 +13,7 @@ import React, { useState, } from 'react'; import cytoscape from 'cytoscape'; +import dagre from 'cytoscape-dagre'; import { debounce } from 'lodash'; import { useTheme } from '../../../hooks/useTheme'; import { @@ -22,6 +23,8 @@ import { } from './cytoscapeOptions'; import { useUiTracker } from '../../../../../observability/public'; +cytoscape.use(dagre); + export const CytoscapeContext = createContext( undefined ); @@ -30,7 +33,6 @@ interface CytoscapeProps { children?: ReactNode; elements: cytoscape.ElementDefinition[]; height: number; - width: number; serviceName?: string; style?: CSSProperties; } @@ -57,59 +59,52 @@ function useCytoscape(options: cytoscape.CytoscapeOptions) { return [ref, cy] as [React.MutableRefObject, cytoscape.Core | undefined]; } -function rotatePoint( - { x, y }: { x: number; y: number }, - degreesRotated: number -) { - const radiansPerDegree = Math.PI / 180; - const θ = radiansPerDegree * degreesRotated; - const cosθ = Math.cos(θ); - const sinθ = Math.sin(θ); - return { - x: x * cosθ - y * sinθ, - y: x * sinθ + y * cosθ, - }; -} - -function getLayoutOptions( - selectedRoots: string[], - height: number, - width: number, - nodeHeight: number -): cytoscape.LayoutOptions { +function getLayoutOptions(nodeHeight: number): cytoscape.LayoutOptions { return { - name: 'breadthfirst', - // @ts-ignore DefinitelyTyped is incorrect here. Roots can be an Array - roots: selectedRoots.length ? selectedRoots : undefined, + name: 'dagre', fit: true, padding: nodeHeight, spacingFactor: 1.2, // @ts-ignore - // Rotate nodes counter-clockwise to transform layout from top→bottom to left→right. - // The extra 5° achieves the effect of separating overlapping taxi-styled edges. - transform: (node: any, pos: cytoscape.Position) => rotatePoint(pos, -95), - // swap width/height of boundingBox to compensate for the rotation - boundingBox: { x1: 0, y1: 0, w: height, h: width }, + nodeSep: nodeHeight, + edgeSep: 32, + rankSep: 128, + rankDir: 'LR', + ranker: 'network-simplex', }; } -function selectRoots(cy: cytoscape.Core): string[] { - const bfs = cy.elements().bfs({ - roots: cy.elements().leaves(), +/* + * @notice + * This product includes code in the function applyCubicBezierStyles that was + * inspired by a public Codepen, which was available under a "MIT" license. + * + * Copyright (c) 2020 by Guillaume (https://codepen.io/guillaumethomas/pen/xxbbBKO) + * MIT License http://www.opensource.org/licenses/mit-license + */ +function applyCubicBezierStyles(edges: cytoscape.EdgeCollection) { + edges.forEach((edge) => { + const { x: x0, y: y0 } = edge.source().position(); + const { x: x1, y: y1 } = edge.target().position(); + const x = x1 - x0; + const y = y1 - y0; + const z = Math.sqrt(x * x + y * y); + const costheta = z === 0 ? 0 : x / z; + const alpha = 0.25; + // Two values for control-point-distances represent a pair symmetric quadratic + // bezier curves joined in the middle as a seamless cubic bezier curve: + edge.style('control-point-distances', [ + -alpha * y * costheta, + alpha * y * costheta, + ]); + edge.style('control-point-weights', [alpha, 1 - alpha]); }); - const furthestNodeFromLeaves = bfs.path.last(); - return cy - .elements() - .roots() - .union(furthestNodeFromLeaves) - .map((el) => el.id()); } export function Cytoscape({ children, elements, height, - width, serviceName, style, }: CytoscapeProps) { @@ -151,13 +146,7 @@ export function Cytoscape({ } else { resetConnectedEdgeStyle(); } - - const selectedRoots = selectRoots(event.cy); - const layout = cy.layout( - getLayoutOptions(selectedRoots, height, width, nodeHeight) - ); - - layout.run(); + cy.layout(getLayoutOptions(nodeHeight)).run(); } }; let layoutstopDelayTimeout: NodeJS.Timeout; @@ -180,6 +169,7 @@ export function Cytoscape({ event.cy.fit(undefined, nodeHeight); } }, 0); + applyCubicBezierStyles(event.cy.edges()); }; // debounce hover tracking so it doesn't spam telemetry with redundant events const trackNodeEdgeHover = debounce( @@ -211,6 +201,9 @@ export function Cytoscape({ console.debug('cytoscape:', event); } }; + const dragHandler: cytoscape.EventHandler = (event) => { + applyCubicBezierStyles(event.target.connectedEdges()); + }; if (cy) { cy.on('data layoutstop select unselect', debugHandler); @@ -220,6 +213,7 @@ export function Cytoscape({ cy.on('mouseout', 'edge, node', mouseoutHandler); cy.on('select', 'node', selectHandler); cy.on('unselect', 'node', unselectHandler); + cy.on('drag', 'node', dragHandler); cy.remove(cy.elements()); cy.add(elements); @@ -239,19 +233,11 @@ export function Cytoscape({ cy.removeListener('mouseout', 'edge, node', mouseoutHandler); cy.removeListener('select', 'node', selectHandler); cy.removeListener('unselect', 'node', unselectHandler); + cy.removeListener('drag', 'node', dragHandler); } clearTimeout(layoutstopDelayTimeout); }; - }, [ - cy, - elements, - height, - serviceName, - trackApmEvent, - width, - nodeHeight, - theme, - ]); + }, [cy, elements, height, serviceName, trackApmEvent, nodeHeight, theme]); return ( diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index 1658c36e8a92f..8291d94d91c48 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -71,6 +71,7 @@ export function Popover({ focusedServiceName }: PopoverProps) { cy.on('select', 'node', selectHandler); cy.on('unselect', 'node', deselect); cy.on('data viewport', deselect); + cy.on('drag', 'node', deselect); } return () => { @@ -78,6 +79,7 @@ export function Popover({ focusedServiceName }: PopoverProps) { cy.removeListener('select', 'node', selectHandler); cy.removeListener('unselect', 'node', deselect); cy.removeListener('data viewport', undefined, deselect); + cy.removeListener('drag', 'node', deselect); } }; }, [cy, deselect]); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx index 2a7d11bb57ca5..5b50eb953d896 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx @@ -49,13 +49,11 @@ storiesOf('app/ServiceMap/Cytoscape', module) }, ]; const height = 300; - const width = 1340; const serviceName = 'opbeans-python'; return ( ); @@ -330,7 +328,7 @@ storiesOf('app/ServiceMap/Cytoscape', module) }, }, ]; - return ; + return ; }, { info: { propTables: false, source: false }, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx index 830e3719b11f9..d8dcc71f5051d 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx @@ -35,6 +35,8 @@ function setSessionJson(json: string) { window.sessionStorage.setItem(SESSION_STORAGE_KEY, json); } +const getCytoscapeHeight = () => window.innerHeight - 300; + storiesOf(STORYBOOK_PATH, module) .addDecorator((storyFn) => {storyFn()}) .add( @@ -43,16 +45,17 @@ storiesOf(STORYBOOK_PATH, module) const [size, setSize] = useState(10); const [json, setJson] = useState(''); const [elements, setElements] = useState( - generateServiceMapElements(size) + generateServiceMapElements({ size, hasAnomalies: true }) ); - return (
{ - setElements(generateServiceMapElements(size)); + setElements( + generateServiceMapElements({ size, hasAnomalies: true }) + ); setJson(''); }} > @@ -79,7 +82,7 @@ storiesOf(STORYBOOK_PATH, module) - + {json && ( - + @@ -204,8 +207,7 @@ storiesOf(STORYBOOK_PATH, module)
); @@ -224,8 +226,7 @@ storiesOf(STORYBOOK_PATH, module)
); @@ -244,8 +245,7 @@ storiesOf(STORYBOOK_PATH, module)
); @@ -264,8 +264,7 @@ storiesOf(STORYBOOK_PATH, module)
); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts index 012256db3ab98..57ef2d49291c4 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/generate_service_map_elements.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSeverity } from '../Popover/getSeverity'; - -export function generateServiceMapElements(size: number): any[] { +export function generateServiceMapElements({ + size, + hasAnomalies, +}: { + size: number; + hasAnomalies: boolean; +}): any[] { const services = range(size).map((i) => { const name = getName(); const anomalyScore = randn(101); @@ -15,11 +19,14 @@ export function generateServiceMapElements(size: number): any[] { 'service.environment': 'production', 'service.name': name, 'agent.name': getAgentName(), - anomaly_score: anomalyScore, - anomaly_severity: getSeverity(anomalyScore), - actual_value: Math.random() * 2000000, - typical_value: Math.random() * 1000000, - ml_job_id: `${name}-request-high_mean_response_time`, + serviceAnomalyStats: hasAnomalies + ? { + transactionType: 'request', + anomalyScore, + actualValue: Math.random() * 2000000, + jobId: `${name}-request-high_mean_response_time`, + } + : undefined, }; }); @@ -146,7 +153,7 @@ const NAMES = [ 'leech', 'loki', 'longshot', - 'lumpkin,', + 'lumpkin', 'madame-web', 'magician', 'magneto', diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 4a271019e06db..9d58ed142dab7 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -168,9 +168,7 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => { { selector: 'edge', style: { - 'curve-style': 'taxi', - // @ts-ignore - 'taxi-direction': 'auto', + 'curve-style': 'unbundled-bezier', 'line-color': lineColor, 'overlay-opacity': 0, 'target-arrow-color': lineColor, @@ -264,7 +262,6 @@ ${theme.eui.euiColorLightShade}`, export const getCytoscapeOptions = ( theme: EuiTheme ): cytoscape.CytoscapeOptions => ({ - autoungrabify: true, boxSelectionEnabled: false, maxZoom: 3, minZoom: 0.2, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index d4be4da2ae1c5..83fab95bc91c9 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -57,7 +57,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { } }, [license, serviceName, urlParams]); - const { ref, height, width } = useRefDimensions(); + const { ref, height } = useRefDimensions(); useTrackPageview({ app: 'apm', path: 'service_map' }); useTrackPageview({ app: 'apm', path: 'service_map', delay: 15000 }); @@ -78,7 +78,6 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { height={height} serviceName={serviceName} style={getCytoscapeDivStyle(theme)} - width={width} > diff --git a/x-pack/plugins/apm/typings/cytoscape_dagre.d.ts b/x-pack/plugins/apm/typings/cytoscape_dagre.d.ts new file mode 100644 index 0000000000000..b5bbdfc14d9d3 --- /dev/null +++ b/x-pack/plugins/apm/typings/cytoscape_dagre.d.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. + */ + +declare module 'cytoscape-dagre'; diff --git a/yarn.lock b/yarn.lock index 56530a0ab4fdc..98092d71caebb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9989,6 +9989,13 @@ cypress@4.11.0: url "0.11.0" yauzl "2.10.0" +cytoscape-dagre@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/cytoscape-dagre/-/cytoscape-dagre-2.2.2.tgz#5f32a85c0ba835f167efee531df9e89ac58ff411" + integrity sha512-zsg36qNwua/L2stJSWkcbSDcvW3E6VZf6KRe6aLnQJxuXuz89tMqI5EVYVKEcNBgzTEzFMFv0PE3T0nD4m6VDw== + dependencies: + dagre "^0.8.2" + cytoscape@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.10.0.tgz#3b462e0d35121ecd2d2702f470915fd6dae01777" @@ -10201,6 +10208,14 @@ d@1: dependencies: es5-ext "^0.10.9" +dagre@^0.8.2: + version "0.8.5" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + damerau-levenshtein@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" @@ -14359,6 +14374,13 @@ graceful-fs@~1.1: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.1.14.tgz#07078db5f6377f6321fceaaedf497de124dc9465" integrity sha1-BweNtfY3f2Mh/Oqu30l94STclGU= +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + graphql-anywhere@^4.1.0-alpha.0: version "4.1.16" resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.16.tgz#82bb59643e30183cfb7b485ed4262a7b39d8a6c1" From 210d6f2df18cce2b78eb075b3d8247559a359944 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Thu, 3 Sep 2020 08:50:03 +0200 Subject: [PATCH 22/43] Legacy SO import: Fix bug causing multiple overrides to only show the last confirm modal (#76482) * Legacy SO import: Fix bug causing multiple overrides to only show the last confirm modal * eslint * fix for loops --- .../public/lib/resolve_saved_objects.ts | 35 ++- .../apps/management/_import_objects.js | 54 +++- ...import_index_patterns_multiple_exists.json | 26 ++ .../_import_objects_multiple_exists.json | 36 +++ .../saved_objects_imports/data.json.gz | Bin 0 -> 1754 bytes .../saved_objects_imports/mappings.json | 244 ++++++++++++++++++ test/functional/page_objects/common_page.ts | 6 +- 7 files changed, 379 insertions(+), 22 deletions(-) create mode 100644 test/functional/apps/management/exports/_import_index_patterns_multiple_exists.json create mode 100644 test/functional/apps/management/exports/_import_objects_multiple_exists.json create mode 100644 test/functional/fixtures/es_archiver/saved_objects_imports/data.json.gz create mode 100644 test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index 79b8c33b84cfe..679ea5ffc23ee 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -160,10 +160,6 @@ function groupByType(docs: SavedObjectsRawDoc[]): Record(list: T[], op: (item: T) => R) { - return await Promise.all(list.map((item) => op(item))); -} - export async function resolveIndexPatternConflicts( resolutions: Array<{ oldId: string; newId: string }>, conflictedIndexPatterns: any[], @@ -175,7 +171,7 @@ export async function resolveIndexPatternConflicts( ) { let importCount = 0; - await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj, doc }) => { + for (const { obj, doc } of conflictedIndexPatterns) { const serializedSearchSource = JSON.parse( doc._source.kibanaSavedObjectMeta?.searchSourceJSON || '{}' ); @@ -220,7 +216,7 @@ export async function resolveIndexPatternConflicts( if (!allResolved) { // The user decided to skip this conflict so do nothing - return; + continue; } obj.searchSource = await dependencies.search.searchSource.create( serializedSearchSourceWithInjectedReferences @@ -228,17 +224,17 @@ export async function resolveIndexPatternConflicts( if (await saveObject(obj, overwriteAll)) { importCount++; } - }); + } return importCount; } export async function saveObjects(objs: SavedObject[], overwriteAll: boolean) { let importCount = 0; - await awaitEachItemInParallel(objs, async (obj) => { + for (const obj of objs) { if (await saveObject(obj, overwriteAll)) { importCount++; } - }); + } return importCount; } @@ -253,16 +249,16 @@ export async function resolveSavedSearches( overwriteAll: boolean ) { let importCount = 0; - await awaitEachItemInParallel(savedSearches, async (searchDoc) => { + for (const searchDoc of savedSearches) { const obj = await getSavedObject(searchDoc, services); if (!obj) { // Just ignore? - return; + continue; } if (await importDocument(obj, searchDoc, overwriteAll)) { importCount++; } - }); + } return importCount; } @@ -280,7 +276,10 @@ export async function resolveSavedObjects( let importedObjectCount = 0; const failedImports: FailedImport[] = []; // Start with the index patterns since everything is dependent on them - await awaitEachItemInParallel(docTypes.indexPatterns, async (indexPatternDoc) => { + // As the confirmation opens a modal, and as we only allow one modal at a time + // (opening a new one close the previous with a rejection) + // we can't do that in parallel + for (const indexPatternDoc of docTypes.indexPatterns) { try { const importedIndexPatternId = await importIndexPattern( indexPatternDoc, @@ -294,7 +293,7 @@ export async function resolveSavedObjects( } catch (error) { failedImports.push({ obj: indexPatternDoc as any, error }); } - }); + } // We want to do the same for saved searches, but we want to keep them separate because they need // to be applied _first_ because other saved objects can be dependent on those saved searches existing @@ -311,7 +310,7 @@ export async function resolveSavedObjects( // likely that these saved objects will work once resaved so keep them around to resave them. const conflictedSavedObjectsLinkedToSavedSearches: any[] = []; - await awaitEachItemInParallel(docTypes.searches, async (searchDoc) => { + for (const searchDoc of docTypes.searches) { const obj = await getSavedObject(searchDoc, services); try { @@ -329,9 +328,9 @@ export async function resolveSavedObjects( failedImports.push({ obj, error }); } } - }); + } - await awaitEachItemInParallel(docTypes.other, async (otherDoc) => { + for (const otherDoc of docTypes.other) { const obj = await getSavedObject(otherDoc, services); try { @@ -350,7 +349,7 @@ export async function resolveSavedObjects( failedImports.push({ obj, error }); } } - }); + } return { conflictedIndexPatterns, diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js index 5fbeb978f9a1c..3941b117e6efe 100644 --- a/test/functional/apps/management/_import_objects.js +++ b/test/functional/apps/management/_import_objects.js @@ -21,6 +21,8 @@ import expect from '@kbn/expect'; import path from 'path'; import { keyBy } from 'lodash'; +const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); @@ -203,12 +205,12 @@ export default function ({ getService, getPageObjects }) { // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); - await esArchiver.load('management'); + await esArchiver.load('saved_objects_imports'); await PageObjects.settings.clickKibanaSavedObjects(); }); afterEach(async function () { - await esArchiver.unload('management'); + await esArchiver.unload('saved_objects_imports'); }); it('should import saved objects', async function () { @@ -280,6 +282,54 @@ export default function ({ getService, getPageObjects }) { expect(isSuccessful).to.be(true); }); + it('should allow the user to confirm overriding multiple duplicate saved objects', async function () { + // This data has already been loaded by the "visualize" esArchive. We'll load it again + // so that we can override the existing visualization. + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_objects_multiple_exists.json'), + false + ); + + await PageObjects.savedObjects.checkImportLegacyWarning(); + await PageObjects.savedObjects.checkImportConflictsWarning(); + + await PageObjects.settings.associateIndexPattern('logstash-*', 'logstash-*'); + await PageObjects.savedObjects.clickConfirmChanges(); + + // Override the visualizations. + await PageObjects.common.clickConfirmOnModal(false); + // as the second confirm can pop instantly, we can't wait for it to be hidden + // with is why we call clickConfirmOnModal with ensureHidden: false in previous statement + // but as the initial popin can take a few ms before fading, we need to wait a little + // to avoid clicking twice on the same modal. + await delay(1000); + await PageObjects.common.clickConfirmOnModal(false); + + const isSuccessful = await testSubjects.exists('importSavedObjectsSuccess'); + expect(isSuccessful).to.be(true); + }); + + it('should allow the user to confirm overriding multiple duplicate index patterns', async function () { + // This data has already been loaded by the "visualize" esArchive. We'll load it again + // so that we can override the existing visualization. + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_index_patterns_multiple_exists.json'), + false + ); + + // Override the index patterns. + await PageObjects.common.clickConfirmOnModal(false); + // as the second confirm can pop instantly, we can't wait for it to be hidden + // with is why we call clickConfirmOnModal with ensureHidden: false in previous statement + // but as the initial popin can take a few ms before fading, we need to wait a little + // to avoid clicking twice on the same modal. + await delay(1000); + await PageObjects.common.clickConfirmOnModal(false); + + const isSuccessful = await testSubjects.exists('importSavedObjectsSuccess'); + expect(isSuccessful).to.be(true); + }); + it('should import saved objects linked to saved searches', async function () { await PageObjects.savedObjects.importFile( path.join(__dirname, 'exports', '_import_objects_saved_search.json') diff --git a/test/functional/apps/management/exports/_import_index_patterns_multiple_exists.json b/test/functional/apps/management/exports/_import_index_patterns_multiple_exists.json new file mode 100644 index 0000000000000..2eb64b1c7ca9f --- /dev/null +++ b/test/functional/apps/management/exports/_import_index_patterns_multiple_exists.json @@ -0,0 +1,26 @@ +[ + { + "_id": "f1e4c910-a2e6-11e7-bb30-233be9be6a20", + "_type": "index-pattern", + "_source": { + "title": "index-pattern-test-1", + "timeFieldName": "@timestamp", + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"expression script\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['bytes'].value\",\"lang\":\"expression\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false}]" + }, + "_meta": { + "savedObjectVersion": 1 + } + }, + { + "_id": "f1e4c910-a2e6-11e7-bb30-233be9be6a87", + "_type": "index-pattern", + "_source": { + "title": "index-pattern-test-2", + "timeFieldName": "@timestamp", + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"expression script\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['bytes'].value\",\"lang\":\"expression\",\"indexed\":true,\"analyzed\":false,\"doc_values\":false}]" + }, + "_meta": { + "savedObjectVersion": 1 + } + } +] diff --git a/test/functional/apps/management/exports/_import_objects_multiple_exists.json b/test/functional/apps/management/exports/_import_objects_multiple_exists.json new file mode 100644 index 0000000000000..9e554aecd9f7a --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects_multiple_exists.json @@ -0,0 +1,36 @@ +[ + { + "_id": "test-1", + "_type": "visualization", + "_source": { + "title": "Visualization test 1", + "visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "uiStateJSON": "{}", + "description": "AreaChart", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-2", + "_type": "visualization", + "_source": { + "title": "Visualization test 2", + "visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}", + "uiStateJSON": "{}", + "description": "AreaChart", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/fixtures/es_archiver/saved_objects_imports/data.json.gz b/test/functional/fixtures/es_archiver/saved_objects_imports/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..006476f867ae7e151f9dae0599cbcc405b3dbfa1 GIT binary patch literal 1754 zcmXYuX*e5*7KTZJXlR7cHn>!-sjb>xTTxpEtwE{%5=#&=mMB79GGuB?Mr~DFNeG>& zQc~;KQpPg2*r}zp7HL(-)<$c)jp?8BJm-79_dP$3TnZQ*?P}x62lT}dFzNx}A$Yz` zH_MQ@>td{R=kqMMk~cE4VRNbs-mN}jCYKC{z(c)8#DhbT6cr1_+oC62S-bRGY4JT9 zf88C`1(7W%GHj8T7yb7y+vriNnueq9O}tC`4~I6J_2dcyj~O~tTG6MZ^>zE>$bQe-4pkkOGUE8z(U&m$oSJ9u184#0 zE@5g7M=*Y6wRL?W@=?X=9|biD=`E$9&f6E9(WxzEK3v(VJJO0{O@RpkT<*OS1NCURwmXp9*5b(+^DTL)VpwLqnNpljdnl|OslEnV8&Y_fY?8m; zEC^A^40CA|_Ld==MVG?r@>w(U#T zx>8wqaNMJ;ifK7a`@OoWMDK&qbn^xJK(Xca#0+p0r#}2^<$3B0l8^byCBUx{o;G0+ za(_NtxUB)i?(t8ttv|F99kxlPSL2dZ^A2w5gKDJq0YN1%vaEVz`ub=MfCD$AHGCK9 zcGPQam?Zi7@*_4t&`zbDm=P%Yi5~vY;!ciY8q1TPEe}i`P|3FYvZ9%NQySxaNZNa< zx02|fmsQ>)wW6@(x)OqT2FlyR{ne7=z`e-?WM*D&7|COVmH)IS*$Q*cK>qH;AJ!+b zptHel)Onhex})8ciuTwJslPAtFZdsrj*_zc)>`XYHL^SVvvOSP(i0AK^F63BE}--5 z$RvKwh`pbo+seeM2D{WaarC`61ENoqI`6)H9~i3A#bPX1G1v*S1RZvfYSFw&Q6m#g z#nuuL5Lrwv`O=27L0X|C?ZbNPM|E+Iu?kCn?1MbO)0a=@bKZn9W7_HQ*x}>=QfGA% zl1#r+b%4inV+Sk!-0Yh_KQ}1f|7~N(sjWpV~w&s{4DbWqy)!PB3>Z<0=SIO~b7M_F32f2f22;y=T6;1w$hQLAHK(PqsiZ{O-z!_ zZ3k^M?|WvOk2K#~09sgwEXvXsxjLpw#_8%FeP#&&1-KZU1Qxhn5B&faft{hDcTJV# zQ5}M>z6~;1;B>jYR0I}~ezf9fa+*vt#x~N|#d+A=;+ZwWhhTw~<#vmIT#KXw$-{xH zQ%?P7=y$!T(zMSPp*K1dTVq}=DAR9eNc*(IKanI`{5}2K)(7^u7K2opxv^?hRB*hFJe(*Vmn02R!sJ`{> zuy!Oe1Bc67;6kx+-tT*6iao}NJv-CHgHFcJR+fUS@=vV8{@*o}3x?l_3@raC559`4 zs4!LKTEJ{v>MQOjxQs9N^Ly*|Z-r_Wnxdq2tMORvq;Vq60iDNil!@>k_nT4{XVS0Y z@V@+&x(|woxX+>x1xtl7N+Rn_v``t=RW)JSAhCyect3kmvx~q1l&Q`J+?bQ_%C1xW t^NEj-opf^L1wA0(B7p8Gm5>BN8IndajLF8F6A0=AAm-&ITT0{O`#*DZMwS2o literal 0 HcmV?d00001 diff --git a/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json new file mode 100644 index 0000000000000..a89fe1dfacfc8 --- /dev/null +++ b/test/functional/fixtures/es_archiver/saved_objects_imports/mappings.json @@ -0,0 +1,244 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "mappings": { + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "strict", + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "dynamic": "strict", + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "dynamic": "strict", + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index d6a96eb651d02..ce7a3f9e132f7 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -332,12 +332,14 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo }); } - async clickConfirmOnModal() { + async clickConfirmOnModal(ensureHidden = true) { log.debug('Clicking modal confirm'); // make sure this data-test-subj 'confirmModalTitleText' exists because we're going to wait for it to be gone later await testSubjects.exists('confirmModalTitleText'); await testSubjects.click('confirmModalConfirmButton'); - await this.ensureModalOverlayHidden(); + if (ensureHidden) { + await this.ensureModalOverlayHidden(); + } } async pressEnterKey() { From 182e0de18f7149f3ed5ddf3768d2903a321c81ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Thu, 3 Sep 2020 08:58:05 +0200 Subject: [PATCH 23/43] [Form lib] Fix regression on field not being validated after reset to its default value. (#76379) --- .../components/use_field.test.tsx | 87 +++++++++++++++++++ .../forms/hook_form_lib/hooks/use_field.ts | 19 ++-- .../hook_form_lib/hooks/use_form.test.tsx | 60 ++++++++++++- 3 files changed, 157 insertions(+), 9 deletions(-) diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index a55b2f0a8fa29..c14471991ccd3 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -64,6 +64,93 @@ describe('', () => { }); }); + describe('validation', () => { + let formHook: FormHook | null = null; + + beforeEach(() => { + formHook = null; + }); + + const onFormHook = (form: FormHook) => { + formHook = form; + }; + + const getTestComp = (fieldConfig: FieldConfig) => { + const TestComp = ({ onForm }: { onForm: (form: FormHook) => void }) => { + const { form } = useForm(); + + useEffect(() => { + onForm(form); + }, [onForm, form]); + + return ( +
+ + + ); + }; + return TestComp; + }; + + const setup = (fieldConfig: FieldConfig) => { + return registerTestBed(getTestComp(fieldConfig), { + memoryRouter: { wrapComponent: false }, + defaultProps: { onForm: onFormHook }, + })() as TestBed; + }; + + test('should update the form validity whenever the field value changes', async () => { + const fieldConfig: FieldConfig = { + defaultValue: '', // empty string, which is not valid + validations: [ + { + validator: ({ value }) => { + // Validate that string is not empty + if ((value as string).trim() === '') { + return { message: 'Error: field is empty.' }; + } + }, + }, + ], + }; + + // Mount our TestComponent + const { + form: { setInputValue }, + } = setup(fieldConfig); + + if (formHook === null) { + throw new Error('FormHook object has not been set.'); + } + + let { isValid } = formHook; + expect(isValid).toBeUndefined(); // Initially the form validity is undefined... + + await act(async () => { + await formHook!.validate(); // ...until we validate the form + }); + + ({ isValid } = formHook); + expect(isValid).toBe(false); + + // Change to a non empty string to pass validation + await act(async () => { + setInputValue('myField', 'changedValue'); + }); + + ({ isValid } = formHook); + expect(isValid).toBe(true); + + // Change back to an empty string to fail validation + await act(async () => { + setInputValue('myField', ''); + }); + + ({ isValid } = formHook); + expect(isValid).toBe(false); + }); + }); + describe('serializer(), deserializer(), formatter()', () => { interface MyForm { name: string; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index fa29f900af2ef..f01c7226ea4ce 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -69,11 +69,12 @@ export const useField = ( const [isChangingValue, setIsChangingValue] = useState(false); const [isValidated, setIsValidated] = useState(false); + const isMounted = useRef(false); const validateCounter = useRef(0); const changeCounter = useRef(0); + const hasBeenReset = useRef(false); const inflightValidation = useRef | null>(null); const debounceTimeout = useRef(null); - const isMounted = useRef(false); // -- HELPERS // ---------------------------------- @@ -142,11 +143,7 @@ export const useField = ( __updateFormDataAt(path, value); // Validate field(s) (that will update form.isValid state) - // We only validate if the value is different than the initial or default value - // to avoid validating after a form.reset() call. - if (value !== initialValue && value !== defaultValue) { - await __validateFields(fieldsToValidateOnChange ?? [path]); - } + await __validateFields(fieldsToValidateOnChange ?? [path]); if (isMounted.current === false) { return; @@ -172,8 +169,6 @@ export const useField = ( }, [ path, value, - defaultValue, - initialValue, valueChangeListener, errorDisplayDelay, fieldsToValidateOnChange, @@ -468,6 +463,7 @@ export const useField = ( setErrors([]); if (resetValue) { + hasBeenReset.current = true; const newValue = deserializeValue(updatedDefaultValue ?? defaultValue); setValue(newValue); return newValue; @@ -539,6 +535,13 @@ export const useField = ( }, [path, __removeField]); useEffect(() => { + // If the field value has been reset, we don't want to call the "onValueChange()" + // as it will set the "isPristine" state to true or validate the field, which initially we don't want. + if (hasBeenReset.current) { + hasBeenReset.current = false; + return; + } + if (!isMounted.current) { return; } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx index 4a880415b6d22..edcd84daf5d2f 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx @@ -22,7 +22,13 @@ import { act } from 'react-dom/test-utils'; import { registerTestBed, getRandomString, TestBed } from '../shared_imports'; import { Form, UseField } from '../components'; -import { FormSubmitHandler, OnUpdateHandler, FormHook, ValidationFunc } from '../types'; +import { + FormSubmitHandler, + OnUpdateHandler, + FormHook, + ValidationFunc, + FieldConfig, +} from '../types'; import { useForm } from './use_form'; interface MyForm { @@ -441,5 +447,57 @@ describe('useForm() hook', () => { deeply: { nested: { value: '' } }, // Fallback to empty string as no config was provided }); }); + + test('should not validate the fields after resetting its value (form validity should be undefined)', async () => { + const fieldConfig: FieldConfig = { + defaultValue: '', + validations: [ + { + validator: ({ value }) => { + if ((value as string).trim() === '') { + return { message: 'Error: empty string' }; + } + }, + }, + ], + }; + + const TestResetComp = () => { + const { form } = useForm(); + + useEffect(() => { + formHook = form; + }, [form]); + + return ( +
+ + + ); + }; + + const { + form: { setInputValue }, + } = registerTestBed(TestResetComp, { + memoryRouter: { wrapComponent: false }, + })() as TestBed; + + let { isValid } = formHook!; + expect(isValid).toBeUndefined(); + + await act(async () => { + setInputValue('myField', 'changedValue'); + }); + ({ isValid } = formHook!); + expect(isValid).toBe(true); + + await act(async () => { + // When we reset the form, value is back to "", which is invalid for the field + formHook!.reset(); + }); + + ({ isValid } = formHook!); + expect(isValid).toBeUndefined(); // Make sure it is "undefined" and not "false". + }); }); }); From 593298ee3147250b91d7c7daf6a768dde4b9a67a Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Thu, 3 Sep 2020 10:20:20 +0200 Subject: [PATCH 24/43] remove legacy kibana plugin (#76064) * remove legacy kibana plugin * as ES as a dependency to security instead of kibana * remove 'kibana' plugin presence check in so mixin * moved the `kibana` config section to the server --- src/legacy/core_plugins/kibana/index.js | 39 ------------------- src/legacy/core_plugins/kibana/package.json | 9 ----- .../core_plugins/kibana/public/index.scss | 7 ---- .../kibana/server/ui_setting_defaults.js | 23 ----------- src/legacy/server/config/schema.js | 9 +++++ .../saved_objects/saved_objects_mixin.js | 8 ---- .../saved_objects/saved_objects_mixin.test.js | 15 ------- x-pack/legacy/plugins/security/index.ts | 2 +- x-pack/legacy/plugins/spaces/index.ts | 2 +- 9 files changed, 11 insertions(+), 103 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/index.js delete mode 100644 src/legacy/core_plugins/kibana/package.json delete mode 100644 src/legacy/core_plugins/kibana/public/index.scss delete mode 100644 src/legacy/core_plugins/kibana/server/ui_setting_defaults.js diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js deleted file mode 100644 index 722d75d00f78f..0000000000000 --- a/src/legacy/core_plugins/kibana/index.js +++ /dev/null @@ -1,39 +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 { getUiSettingDefaults } from './server/ui_setting_defaults'; - -export default function (kibana) { - return new kibana.Plugin({ - id: 'kibana', - config: function (Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - index: Joi.string().default('.kibana'), - autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), - // TODO Also allow units here like in elasticsearch config once this is moved to the new platform - autocompleteTimeout: Joi.number().integer().min(1).default(1000), - }).default(); - }, - - uiExports: { - uiSettingDefaults: getUiSettingDefaults(), - }, - }); -} diff --git a/src/legacy/core_plugins/kibana/package.json b/src/legacy/core_plugins/kibana/package.json deleted file mode 100644 index 94db646611df0..0000000000000 --- a/src/legacy/core_plugins/kibana/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "kibana", - "version": "kibana", - "config": { - "@elastic/eslint-import-resolver-kibana": { - "projectRoot": false - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss deleted file mode 100644 index 7de0c8fc15f94..0000000000000 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Elastic charts -@import '@elastic/charts/dist/theme'; -@import '@elastic/eui/src/themes/charts/theme'; - -// Public UI styles -@import 'src/legacy/ui/public/index'; - diff --git a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/server/ui_setting_defaults.js deleted file mode 100644 index 7de5fb581643a..0000000000000 --- a/src/legacy/core_plugins/kibana/server/ui_setting_defaults.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. - */ - -export function getUiSettingDefaults() { - // wrapped in provider so that a new instance is given to each app/test - return {}; -} diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 6cd3f8dc448b0..dd65e45659ffc 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -231,6 +231,15 @@ export default () => locale: Joi.string().default('en'), }).default(), + // temporarily moved here from the (now deleted) kibana legacy plugin + kibana: Joi.object({ + enabled: Joi.boolean().default(true), + index: Joi.string().default('.kibana'), + autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), + // TODO Also allow units here like in elasticsearch config once this is moved to the new platform + autocompleteTimeout: Joi.number().integer().min(1).default(1000), + }).default(), + savedObjects: Joi.object({ maxImportPayloadBytes: Joi.number().default(10485760), maxImportExportSize: Joi.number().default(10000), diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index 185c8807ae8b5..96cf2058839cf 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -39,14 +39,6 @@ export function savedObjectsMixin(kbnServer, server) { server.decorate('server', 'kibanaMigrator', migrator); - const warn = (message) => server.log(['warning', 'saved-objects'], message); - // we use kibana.index which is technically defined in the kibana plugin, so if - // we don't have the plugin (mainly tests) we can't initialize the saved objects - if (!kbnServer.pluginSpecs.some((p) => p.getId() === 'kibana')) { - warn('Saved Objects uninitialized because the Kibana plugin is disabled.'); - return; - } - const serializer = kbnServer.newPlatform.start.core.savedObjects.createSerializer(); const createRepository = (callCluster, includedHiddenTypes = []) => { diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js index 63e4a632ab5e0..d1d6c052ad589 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js @@ -161,21 +161,6 @@ describe('Saved Objects Mixin', () => { }; }); - describe('no kibana plugin', () => { - it('should not try to create anything', () => { - mockKbnServer.pluginSpecs.some = () => false; - savedObjectsMixin(mockKbnServer, mockServer); - expect(mockServer.log).toHaveBeenCalledWith(expect.any(Array), expect.any(String)); - expect(mockServer.decorate).toHaveBeenCalledWith( - 'server', - 'kibanaMigrator', - expect.any(Object) - ); - expect(mockServer.decorate).toHaveBeenCalledTimes(1); - expect(mockServer.route).not.toHaveBeenCalled(); - }); - }); - describe('Saved object service', () => { let service; diff --git a/x-pack/legacy/plugins/security/index.ts b/x-pack/legacy/plugins/security/index.ts index 3ab5126e52c5f..c3596d3745e57 100644 --- a/x-pack/legacy/plugins/security/index.ts +++ b/x-pack/legacy/plugins/security/index.ts @@ -11,7 +11,7 @@ export const security = (kibana: Record) => new kibana.Plugin({ id: 'security', publicDir: resolve(__dirname, 'public'), - require: ['kibana'], + require: ['elasticsearch'], configPrefix: 'xpack.security', config: (Joi: Root) => Joi.object({ enabled: Joi.boolean().default(true) }) diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 40339f198df6d..725d022879e0d 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -16,7 +16,7 @@ export const spaces = (kibana: Record) => id: 'spaces', configPrefix: 'xpack.spaces', publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], + require: ['elasticsearch', 'xpack_main'], config(Joi: any) { return Joi.object({ enabled: Joi.boolean().default(true), From ebc50e6c691a2483485f94a23314a8bb1a6ec5a0 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 3 Sep 2020 10:45:28 +0200 Subject: [PATCH 25/43] [Ingest Manager] Wording & linking improvements (#76284) * Improve wording on add agent flyout. * Add agent explanation to agent flyout. * Add link to data stream page. --- .../components/agent_enrollment_flyout/index.tsx | 10 ++++++++++ .../managed_instructions.tsx | 6 +++--- .../standalone_instructions.tsx | 16 ++++++++++++++-- .../plugins/translations/translations/ja-JP.json | 2 -- .../plugins/translations/translations/zh-CN.json | 2 -- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx index 414c4e0d7ad2c..05817f28f9292 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/index.tsx @@ -8,6 +8,8 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, + EuiSpacer, + EuiText, EuiTitle, EuiFlexGroup, EuiFlexItem, @@ -44,6 +46,14 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ /> + + + + + setMode('managed')}> = ({ agentPolic <> ), diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx index 049ceca82b309..9262cc2cb42ac 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_enrollment_flyout/standalone_instructions.tsx @@ -15,12 +15,13 @@ import { EuiFlexGroup, EuiCodeBlock, EuiCopy, + EuiLink, } from '@elastic/eui'; import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AgentPolicy } from '../../../../types'; -import { useCore, sendGetOneAgentPolicyFull } from '../../../../hooks'; +import { useCore, useLink, sendGetOneAgentPolicyFull } from '../../../../hooks'; import { DownloadStep, AgentPolicySelectionStep } from './steps'; import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../../../services'; @@ -31,6 +32,7 @@ interface Props { const RUN_INSTRUCTIONS = './elastic-agent run'; export const StandaloneInstructions: React.FunctionComponent = ({ agentPolicies }) => { + const { getHref } = useLink(); const core = useCore(); const { notifications } = core; @@ -157,7 +159,17 @@ export const StandaloneInstructions: React.FunctionComponent = ({ agentPo + + + ), + }} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 59d952d08c181..936f5aaf6522d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9119,10 +9119,8 @@ "xpack.ingestManager.agentEnrollment.enrollStandaloneTabLabel": "スタンドアロンモード", "xpack.ingestManager.agentEnrollment.fleetNotInitializedText": "エージェントを登録する前に、フリートを設定する必要があります。{link}", "xpack.ingestManager.agentEnrollment.flyoutTitle": "エージェントの追加", - "xpack.ingestManager.agentEnrollment.goToFleetButton": "フリートに移動します。", "xpack.ingestManager.agentEnrollment.managedDescription": "必要なエージェントの数に関係なく、Fleetでは、簡単に一元的に更新を管理し、エージェントにデプロイすることができます。次の手順に従い、Elasticエージェントをダウンロードし、Fleetに登録してください。", "xpack.ingestManager.agentEnrollment.standaloneDescription": "スタンドアロンモードで実行中のエージェントは、構成を変更したい場合には、手動で更新する必要があります。次の手順に従い、スタンドアロンモードでElasticエージェントをダウンロードし、セットアップしてください。", - "xpack.ingestManager.agentEnrollment.stepCheckForDataDescription": "エージェントを起動した後、エージェントはデータの送信を開始します。Ingest Managerのデータセットページでは、このデータを確認できます。", "xpack.ingestManager.agentEnrollment.stepCheckForDataTitle": "データを確認", "xpack.ingestManager.agentEnrollment.stepChooseAgentPolicyTitle": "エージェント構成を選択", "xpack.ingestManager.agentEnrollment.stepConfigureAgentDescription": "この構成をコピーし、Elasticエージェントがインストールされているシステムのファイル{fileName}に置きます。必ず、構成ファイルの{outputSection}セクションで{ESUsernameVariable}と{ESPasswordVariable}を修正し、実際のElasticsearch認証資格情報が使用されるようにしてください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6641eede91e66..800439f7b7b3f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9122,10 +9122,8 @@ "xpack.ingestManager.agentEnrollment.enrollStandaloneTabLabel": "独立模式", "xpack.ingestManager.agentEnrollment.fleetNotInitializedText": "注册代理前需要设置 Fleet。{link}", "xpack.ingestManager.agentEnrollment.flyoutTitle": "添加代理", - "xpack.ingestManager.agentEnrollment.goToFleetButton": "前往 Fleet。", "xpack.ingestManager.agentEnrollment.managedDescription": "无论是需要一个代理还是需要数以千计的代理,Fleet 允许您轻松地集中管理并部署代理的更新。按照下面的说明下载 Elastic 代理并将代理注册到 Fleet。", "xpack.ingestManager.agentEnrollment.standaloneDescription": "如果希望对以独立模式运行的代理进行配置更改,则需要手动更新。按照下面的说明下载并设置独立模式的 Elastic 代理。", - "xpack.ingestManager.agentEnrollment.stepCheckForDataDescription": "启动代理后,代理应开始发送数据。您可以在采集管理器的数据集页面查看此数据。", "xpack.ingestManager.agentEnrollment.stepCheckForDataTitle": "检查数据", "xpack.ingestManager.agentEnrollment.stepChooseAgentPolicyTitle": "选择代理配置", "xpack.ingestManager.agentEnrollment.stepConfigureAgentDescription": "在安装 Elastic 代理的系统上复制此配置并将其放入名为 {fileName} 的文件中。切勿忘记修改配置文件中 {outputSection} 部分的{ESUsernameVariable} 和 {ESPasswordVariable},以便其使用您的实际 Elasticsearch 凭据。", From cd86b81b82f3cf28edc47f56bab900bd43214d9c Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 3 Sep 2020 11:57:03 +0300 Subject: [PATCH 26/43] Move streams utils to the core (#76397) * move utils/streams to the KP * allow imports from src/core/server/utils * ts-ify * import streams from KP * remove unnecessary ts-expect-error * fix kbn-es-archiver build * lost export * copy array in createListStream * remove streams from legacy utils Co-authored-by: spalger --- .eslintrc.js | 2 ++ src/cli_keystore/add.js | 2 +- .../get_sorted_objects_for_export.test.ts | 2 +- .../export/get_sorted_objects_for_export.ts | 2 +- .../import/collect_saved_objects.ts | 2 +- .../import/create_limit_stream.test.ts | 2 +- .../server/saved_objects/routes/export.ts | 6 +--- .../routes/integration_tests/export.test.ts | 2 +- .../server/saved_objects/routes/utils.test.ts | 2 +- src/core/server/saved_objects/routes/utils.ts | 6 +--- src/core/server/utils/index.ts | 1 + .../utils/streams/concat_stream.test.ts} | 2 +- .../server/utils/streams/concat_stream.ts} | 2 +- .../streams/concat_stream_providers.test.ts} | 0 .../utils/streams/concat_stream_providers.ts} | 7 ++-- .../utils/streams/filter_stream.test.ts | 2 +- .../server}/utils/streams/filter_stream.ts | 0 .../server/utils/streams/index.ts} | 0 .../utils/streams/intersperse_stream.test.ts} | 2 +- .../utils/streams/intersperse_stream.ts} | 4 +-- .../server/utils/streams/list_stream.test.ts} | 2 +- .../server/utils/streams/list_stream.ts} | 4 +-- .../server/utils/streams/map_stream.test.ts} | 4 +-- .../server/utils/streams/map_stream.ts} | 2 +- .../streams/promise_from_streams.test.ts} | 7 ++-- .../utils/streams/promise_from_streams.ts} | 16 ++++++--- .../utils/streams/reduce_stream.test.ts} | 11 +++--- .../server/utils/streams/reduce_stream.ts} | 5 ++- .../utils/streams/replace_stream.test.ts} | 12 ++++--- .../server/utils/streams/replace_stream.ts} | 3 +- .../utils/streams/split_stream.test.ts} | 8 ++--- .../server/utils/streams/split_stream.ts} | 6 ++-- src/dev/build/lib/watch_stdio_for_line.ts | 2 +- src/legacy/server/i18n/index.ts | 1 - .../server/logging/log_format_json.test.js | 2 +- .../server/logging/log_format_string.test.js | 2 +- src/legacy/utils/artifact_type.ts | 1 - src/legacy/utils/index.d.ts | 13 ------- src/legacy/utils/index.js | 12 ------- src/legacy/utils/streams/index.d.ts | 36 ------------------- .../routes/rules/import_rules_route.ts | 2 +- .../routes/rules/utils.test.ts | 2 +- .../create_rules_stream_from_ndjson.test.ts | 2 +- .../rules/create_rules_stream_from_ndjson.ts | 2 +- .../create_timelines_stream_from_ndjson.ts | 2 +- .../routes/import_timelines_route.test.ts | 4 +-- .../lib/timeline/routes/utils/common.ts | 2 +- .../timeline/routes/utils/import_timelines.ts | 2 +- .../utils/install_prepacked_timelines.test.ts | 2 +- .../read_stream/create_stream_from_ndjson.ts | 2 +- .../create_copy_to_space_mocks.ts | 2 +- 51 files changed, 85 insertions(+), 136 deletions(-) rename src/{legacy/utils/streams/concat_stream.test.js => core/server/utils/streams/concat_stream.test.ts} (98%) rename src/{legacy/utils/streams/concat_stream.js => core/server/utils/streams/concat_stream.ts} (96%) rename src/{legacy/utils/streams/concat_stream_providers.test.js => core/server/utils/streams/concat_stream_providers.test.ts} (100%) rename src/{legacy/utils/streams/concat_stream_providers.js => core/server/utils/streams/concat_stream_providers.ts} (91%) rename src/{legacy => core/server}/utils/streams/filter_stream.test.ts (99%) rename src/{legacy => core/server}/utils/streams/filter_stream.ts (100%) rename src/{legacy/utils/streams/index.js => core/server/utils/streams/index.ts} (100%) rename src/{legacy/utils/streams/intersperse_stream.test.js => core/server/utils/streams/intersperse_stream.test.ts} (99%) rename src/{legacy/utils/streams/intersperse_stream.js => core/server/utils/streams/intersperse_stream.ts} (95%) rename src/{legacy/utils/streams/list_stream.test.js => core/server/utils/streams/list_stream.test.ts} (97%) rename src/{legacy/utils/streams/list_stream.js => core/server/utils/streams/list_stream.ts} (90%) rename src/{legacy/utils/streams/map_stream.test.js => core/server/utils/streams/map_stream.test.ts} (95%) rename src/{legacy/utils/streams/map_stream.js => core/server/utils/streams/map_stream.ts} (93%) rename src/{legacy/utils/streams/promise_from_streams.test.js => core/server/utils/streams/promise_from_streams.test.ts} (96%) rename src/{legacy/utils/streams/promise_from_streams.js => core/server/utils/streams/promise_from_streams.ts} (80%) rename src/{legacy/utils/streams/reduce_stream.test.js => core/server/utils/streams/reduce_stream.test.ts} (92%) rename src/{legacy/utils/streams/reduce_stream.js => core/server/utils/streams/reduce_stream.ts} (95%) rename src/{legacy/utils/streams/replace_stream.test.js => core/server/utils/streams/replace_stream.test.ts} (91%) rename src/{legacy/utils/streams/replace_stream.js => core/server/utils/streams/replace_stream.ts} (96%) rename src/{legacy/utils/streams/split_stream.test.js => core/server/utils/streams/split_stream.test.ts} (93%) rename src/{legacy/utils/streams/split_stream.js => core/server/utils/streams/split_stream.ts} (95%) delete mode 100644 src/legacy/utils/streams/index.d.ts diff --git a/.eslintrc.js b/.eslintrc.js index 9b75c36c95abd..3161a25b70870 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -305,6 +305,8 @@ module.exports = { '!src/core/server/mocks{,.ts}', '!src/core/server/types{,.ts}', '!src/core/server/test_utils{,.ts}', + '!src/core/server/utils', // ts alias + '!src/core/server/utils/**/*', // for absolute imports until fixed in // https://github.com/elastic/kibana/issues/36096 '!src/core/server/*.test.mocks{,.ts}', diff --git a/src/cli_keystore/add.js b/src/cli_keystore/add.js index 44737e387c2d2..462259ec942dd 100644 --- a/src/cli_keystore/add.js +++ b/src/cli_keystore/add.js @@ -19,7 +19,7 @@ import { Logger } from '../cli_plugin/lib/logger'; import { confirm, question } from '../legacy/server/utils'; -import { createPromiseFromStreams, createConcatStream } from '../legacy/utils'; +import { createPromiseFromStreams, createConcatStream } from '../core/server/utils'; /** * @param {Keystore} keystore diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index 85b3a281aef7f..c084125f43127 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -20,7 +20,7 @@ import { exportSavedObjectsToStream } from './get_sorted_objects_for_export'; import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; import { Readable } from 'stream'; -import { createPromiseFromStreams, createConcatStream } from '../../../../legacy/utils/streams'; +import { createPromiseFromStreams, createConcatStream } from '../../utils/streams'; async function readStreamToCompletion(stream: Readable) { return createPromiseFromStreams([stream, createConcatStream([])]); diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index 94f727e238ecf..214b51db7dd6b 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -18,7 +18,7 @@ */ import Boom from 'boom'; -import { createListStream } from '../../../../legacy/utils/streams'; +import { createListStream } from '../../utils/streams'; import { SavedObjectsClientContract, SavedObject } from '../types'; import { fetchNestedDependencies } from './inject_nested_depdendencies'; import { sortObjects } from './sort_objects'; diff --git a/src/core/server/saved_objects/import/collect_saved_objects.ts b/src/core/server/saved_objects/import/collect_saved_objects.ts index f55e6bf0d2af4..8e84f864cf449 100644 --- a/src/core/server/saved_objects/import/collect_saved_objects.ts +++ b/src/core/server/saved_objects/import/collect_saved_objects.ts @@ -23,7 +23,7 @@ import { createFilterStream, createMapStream, createPromiseFromStreams, -} from '../../../../legacy/utils/streams'; +} from '../../utils/streams'; import { SavedObject } from '../types'; import { createLimitStream } from './create_limit_stream'; import { SavedObjectsImportError } from './types'; diff --git a/src/core/server/saved_objects/import/create_limit_stream.test.ts b/src/core/server/saved_objects/import/create_limit_stream.test.ts index 736cfadcb6222..a7e689710a564 100644 --- a/src/core/server/saved_objects/import/create_limit_stream.test.ts +++ b/src/core/server/saved_objects/import/create_limit_stream.test.ts @@ -21,7 +21,7 @@ import { createConcatStream, createListStream, createPromiseFromStreams, -} from '../../../../legacy/utils/streams'; +} from '../../utils/streams'; import { createLimitStream } from './create_limit_stream'; describe('createLimitStream()', () => { diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 9445c144ecda4..35a65d8d9651f 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -19,11 +19,7 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; -import { - createPromiseFromStreams, - createMapStream, - createConcatStream, -} from '../../../../legacy/utils/streams'; +import { createPromiseFromStreams, createMapStream, createConcatStream } from '../../utils/streams'; import { IRouter } from '../../http'; import { SavedObjectConfig } from '../saved_objects_config'; import { exportSavedObjectsToStream } from '../export'; diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index d47f7c6050d8f..a3891712fd22b 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -22,7 +22,7 @@ jest.mock('../../export', () => ({ })); import * as exportMock from '../../export'; -import { createListStream } from '../../../../../legacy/utils/streams'; +import { createListStream } from '../../../utils/streams'; import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectConfig } from '../../saved_objects_config'; diff --git a/src/core/server/saved_objects/routes/utils.test.ts b/src/core/server/saved_objects/routes/utils.test.ts index 24719724785af..fd3bdad8606ed 100644 --- a/src/core/server/saved_objects/routes/utils.test.ts +++ b/src/core/server/saved_objects/routes/utils.test.ts @@ -19,7 +19,7 @@ import { createSavedObjectsStreamFromNdJson, validateTypes, validateObjects } from './utils'; import { Readable } from 'stream'; -import { createPromiseFromStreams, createConcatStream } from '../../../../legacy/utils/streams'; +import { createPromiseFromStreams, createConcatStream } from '../../utils/streams'; async function readStreamToCompletion(stream: Readable) { return createPromiseFromStreams([stream, createConcatStream([])]); diff --git a/src/core/server/saved_objects/routes/utils.ts b/src/core/server/saved_objects/routes/utils.ts index 3963833a9c718..f16a6e471257d 100644 --- a/src/core/server/saved_objects/routes/utils.ts +++ b/src/core/server/saved_objects/routes/utils.ts @@ -19,11 +19,7 @@ import { Readable } from 'stream'; import { SavedObject, SavedObjectsExportResultDetails } from 'src/core/server'; -import { - createSplitStream, - createMapStream, - createFilterStream, -} from '../../../../legacy/utils/streams'; +import { createSplitStream, createMapStream, createFilterStream } from '../../utils/streams'; export function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { return ndJsonStream diff --git a/src/core/server/utils/index.ts b/src/core/server/utils/index.ts index b01a4c4e04899..d9c4217c4117f 100644 --- a/src/core/server/utils/index.ts +++ b/src/core/server/utils/index.ts @@ -20,3 +20,4 @@ export * from './crypto'; export * from './from_root'; export * from './package_json'; +export * from './streams'; diff --git a/src/legacy/utils/streams/concat_stream.test.js b/src/core/server/utils/streams/concat_stream.test.ts similarity index 98% rename from src/legacy/utils/streams/concat_stream.test.js rename to src/core/server/utils/streams/concat_stream.test.ts index 1498334013d1a..e964ab2a7a97e 100644 --- a/src/legacy/utils/streams/concat_stream.test.js +++ b/src/core/server/utils/streams/concat_stream.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createListStream, createPromiseFromStreams, createConcatStream } from './'; +import { createListStream, createPromiseFromStreams, createConcatStream } from './index'; describe('concatStream', () => { test('accepts an initial value', async () => { diff --git a/src/legacy/utils/streams/concat_stream.js b/src/core/server/utils/streams/concat_stream.ts similarity index 96% rename from src/legacy/utils/streams/concat_stream.js rename to src/core/server/utils/streams/concat_stream.ts index e3f8f7261d2b7..03450cb51b832 100644 --- a/src/legacy/utils/streams/concat_stream.js +++ b/src/core/server/utils/streams/concat_stream.ts @@ -41,6 +41,6 @@ import { createReduceStream } from './reduce_stream'; * items will concat with * @return {Transform} */ -export function createConcatStream(initial) { +export function createConcatStream(initial?: T) { return createReduceStream((acc, chunk) => acc.concat(chunk), initial); } diff --git a/src/legacy/utils/streams/concat_stream_providers.test.js b/src/core/server/utils/streams/concat_stream_providers.test.ts similarity index 100% rename from src/legacy/utils/streams/concat_stream_providers.test.js rename to src/core/server/utils/streams/concat_stream_providers.test.ts diff --git a/src/legacy/utils/streams/concat_stream_providers.js b/src/core/server/utils/streams/concat_stream_providers.ts similarity index 91% rename from src/legacy/utils/streams/concat_stream_providers.js rename to src/core/server/utils/streams/concat_stream_providers.ts index 11dfb84284df3..bb836e3d73787 100644 --- a/src/legacy/utils/streams/concat_stream_providers.js +++ b/src/core/server/utils/streams/concat_stream_providers.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PassThrough } from 'stream'; +import { Readable, PassThrough, TransformOptions } from 'stream'; /** * Write the data and errors from a list of stream providers @@ -29,7 +29,10 @@ import { PassThrough } from 'stream'; * @param {PassThroughOptions} options options passed to the PassThrough constructor * @return {WritableStream} combined stream */ -export function concatStreamProviders(sourceProviders, options = {}) { +export function concatStreamProviders( + sourceProviders: Array<() => Readable>, + options?: TransformOptions +) { const destination = new PassThrough(options); const queue = sourceProviders.slice(); diff --git a/src/legacy/utils/streams/filter_stream.test.ts b/src/core/server/utils/streams/filter_stream.test.ts similarity index 99% rename from src/legacy/utils/streams/filter_stream.test.ts rename to src/core/server/utils/streams/filter_stream.test.ts index 28b7f2588628e..41073e54b0a84 100644 --- a/src/legacy/utils/streams/filter_stream.test.ts +++ b/src/core/server/utils/streams/filter_stream.test.ts @@ -22,7 +22,7 @@ import { createFilterStream, createListStream, createPromiseFromStreams, -} from './'; +} from './index'; describe('createFilterStream()', () => { test('calls the function with each item in the source stream', async () => { diff --git a/src/legacy/utils/streams/filter_stream.ts b/src/core/server/utils/streams/filter_stream.ts similarity index 100% rename from src/legacy/utils/streams/filter_stream.ts rename to src/core/server/utils/streams/filter_stream.ts diff --git a/src/legacy/utils/streams/index.js b/src/core/server/utils/streams/index.ts similarity index 100% rename from src/legacy/utils/streams/index.js rename to src/core/server/utils/streams/index.ts diff --git a/src/legacy/utils/streams/intersperse_stream.test.js b/src/core/server/utils/streams/intersperse_stream.test.ts similarity index 99% rename from src/legacy/utils/streams/intersperse_stream.test.js rename to src/core/server/utils/streams/intersperse_stream.test.ts index e11b36d77106a..9aa15035d2a1c 100644 --- a/src/legacy/utils/streams/intersperse_stream.test.js +++ b/src/core/server/utils/streams/intersperse_stream.test.ts @@ -22,7 +22,7 @@ import { createListStream, createIntersperseStream, createConcatStream, -} from './'; +} from './index'; describe('intersperseStream', () => { test('places the intersperse value between each provided value', async () => { diff --git a/src/legacy/utils/streams/intersperse_stream.js b/src/core/server/utils/streams/intersperse_stream.ts similarity index 95% rename from src/legacy/utils/streams/intersperse_stream.js rename to src/core/server/utils/streams/intersperse_stream.ts index 5f9f0b03cd7eb..272507221caff 100644 --- a/src/legacy/utils/streams/intersperse_stream.js +++ b/src/core/server/utils/streams/intersperse_stream.ts @@ -40,7 +40,7 @@ import { Transform } from 'stream'; * @param {String|Buffer} intersperseChunk * @return {Transform} */ -export function createIntersperseStream(intersperseChunk) { +export function createIntersperseStream(intersperseChunk: string | Buffer) { let first = true; return new Transform({ @@ -55,7 +55,7 @@ export function createIntersperseStream(intersperseChunk) { } this.push(chunk); - callback(null); + callback(); } catch (err) { callback(err); } diff --git a/src/legacy/utils/streams/list_stream.test.js b/src/core/server/utils/streams/list_stream.test.ts similarity index 97% rename from src/legacy/utils/streams/list_stream.test.js rename to src/core/server/utils/streams/list_stream.test.ts index 12e20696b0510..2a20c929db6b9 100644 --- a/src/legacy/utils/streams/list_stream.test.js +++ b/src/core/server/utils/streams/list_stream.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createListStream } from './'; +import { createListStream } from './index'; describe('listStream', () => { test('provides the values in the initial list', async () => { diff --git a/src/legacy/utils/streams/list_stream.js b/src/core/server/utils/streams/list_stream.ts similarity index 90% rename from src/legacy/utils/streams/list_stream.js rename to src/core/server/utils/streams/list_stream.ts index a614620b054b7..e62f6d3fa930b 100644 --- a/src/legacy/utils/streams/list_stream.js +++ b/src/core/server/utils/streams/list_stream.ts @@ -26,8 +26,8 @@ import { Readable } from 'stream'; * @param {Array} items - the list of items to provide * @return {Readable} */ -export function createListStream(items = []) { - const queue = [].concat(items); +export function createListStream(items: T | T[] = []) { + const queue = Array.isArray(items) ? [...items] : [items]; return new Readable({ objectMode: true, diff --git a/src/legacy/utils/streams/map_stream.test.js b/src/core/server/utils/streams/map_stream.test.ts similarity index 95% rename from src/legacy/utils/streams/map_stream.test.js rename to src/core/server/utils/streams/map_stream.test.ts index d86da178f0c1b..bf0cab39c21f4 100644 --- a/src/legacy/utils/streams/map_stream.test.js +++ b/src/core/server/utils/streams/map_stream.test.ts @@ -39,7 +39,7 @@ describe('createMapStream()', () => { test('send the return value from the mapper on the output stream', async () => { const result = await createPromiseFromStreams([ createListStream([1, 2, 3]), - createMapStream((n) => n * 100), + createMapStream((n: number) => n * 100), createConcatStream([]), ]); @@ -49,7 +49,7 @@ describe('createMapStream()', () => { test('supports async mappers', async () => { const result = await createPromiseFromStreams([ createListStream([1, 2, 3]), - createMapStream(async (n, i) => { + createMapStream(async (n: number, i: number) => { await delay(n); return n * i; }), diff --git a/src/legacy/utils/streams/map_stream.js b/src/core/server/utils/streams/map_stream.ts similarity index 93% rename from src/legacy/utils/streams/map_stream.js rename to src/core/server/utils/streams/map_stream.ts index 4e906471330f1..aad53cc526626 100644 --- a/src/legacy/utils/streams/map_stream.js +++ b/src/core/server/utils/streams/map_stream.ts @@ -19,7 +19,7 @@ import { Transform } from 'stream'; -export function createMapStream(fn) { +export function createMapStream(fn: (value: T, i: number) => void) { let i = 0; return new Transform({ diff --git a/src/legacy/utils/streams/promise_from_streams.test.js b/src/core/server/utils/streams/promise_from_streams.test.ts similarity index 96% rename from src/legacy/utils/streams/promise_from_streams.test.js rename to src/core/server/utils/streams/promise_from_streams.test.ts index e4d9835106f12..1f2596c16a6fa 100644 --- a/src/legacy/utils/streams/promise_from_streams.test.js +++ b/src/core/server/utils/streams/promise_from_streams.test.ts @@ -19,7 +19,7 @@ import { Readable, Writable, Duplex, Transform } from 'stream'; -import { createListStream, createPromiseFromStreams, createReduceStream } from './'; +import { createListStream, createPromiseFromStreams, createReduceStream } from './index'; describe('promiseFromStreams', () => { test('pipes together an array of streams', async () => { @@ -76,14 +76,13 @@ describe('promiseFromStreams', () => { test('waits for writing and resolves to final value', async () => { let written = ''; - const duplexReadQueue = []; + const duplexReadQueue: Array> = []; const duplexItemsToPush = ['foo', 'bar', null]; const result = await createPromiseFromStreams([ createListStream(['a', 'b', 'c']), new Duplex({ async read() { - const result = await duplexReadQueue.shift(); - this.push(result); + this.push(await duplexReadQueue.shift()); }, write(chunk, enc, cb) { diff --git a/src/legacy/utils/streams/promise_from_streams.js b/src/core/server/utils/streams/promise_from_streams.ts similarity index 80% rename from src/legacy/utils/streams/promise_from_streams.js rename to src/core/server/utils/streams/promise_from_streams.ts index 05f6a08aa1a09..f5fc4af62bc83 100644 --- a/src/legacy/utils/streams/promise_from_streams.js +++ b/src/core/server/utils/streams/promise_from_streams.ts @@ -34,16 +34,20 @@ * @return {Promise} */ -import { pipeline, Writable } from 'stream'; +import { pipeline, Writable, Readable } from 'stream'; -export async function createPromiseFromStreams(streams) { - let finalChunk; +function isReadable(stream: Readable | Writable): stream is Readable { + return 'read' in stream && typeof stream.read === 'function'; +} + +export async function createPromiseFromStreams(streams: [Readable, ...Writable[]]): Promise { + let finalChunk: any; const last = streams[streams.length - 1]; - if (typeof last.read !== 'function' && streams.length === 1) { + if (!isReadable(last) && streams.length === 1) { // For a nicer error than what stream.pipeline throws throw new Error('A minimum of 2 streams is required when a non-readable stream is given'); } - if (typeof last.read === 'function') { + if (isReadable(last)) { // We are pushing a writable stream to capture the last chunk streams.push( new Writable({ @@ -57,7 +61,9 @@ export async function createPromiseFromStreams(streams) { }) ); } + return new Promise((resolve, reject) => { + // @ts-expect-error 'pipeline' doesn't support variable length of arguments pipeline(...streams, (err) => { if (err) return reject(err); resolve(finalChunk); diff --git a/src/legacy/utils/streams/reduce_stream.test.js b/src/core/server/utils/streams/reduce_stream.test.ts similarity index 92% rename from src/legacy/utils/streams/reduce_stream.test.js rename to src/core/server/utils/streams/reduce_stream.test.ts index 2c073f67f82a8..e4a7dc1cef491 100644 --- a/src/legacy/utils/streams/reduce_stream.test.js +++ b/src/core/server/utils/streams/reduce_stream.test.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +import { Transform } from 'stream'; +import { createReduceStream, createPromiseFromStreams, createListStream } from './index'; -import { createReduceStream, createPromiseFromStreams, createListStream } from './'; - -const promiseFromEvent = (name, emitter) => +const promiseFromEvent = (name: string, emitter: Transform) => new Promise((resolve) => emitter.on(name, () => resolve(name))); describe('reduceStream', () => { @@ -47,7 +47,10 @@ describe('reduceStream', () => { }); test('emits an error if an iteration fails', async () => { - const reduce = createReduceStream((acc, i) => expect(i).toBe(1), 0); + const reduce = createReduceStream((acc, i) => { + expect(i).toBe(1); + return acc; + }, 0); const errorEvent = promiseFromEvent('error', reduce); reduce.write(1); diff --git a/src/legacy/utils/streams/reduce_stream.js b/src/core/server/utils/streams/reduce_stream.ts similarity index 95% rename from src/legacy/utils/streams/reduce_stream.js rename to src/core/server/utils/streams/reduce_stream.ts index d66b0124d1dab..9129df096ad13 100644 --- a/src/legacy/utils/streams/reduce_stream.js +++ b/src/core/server/utils/streams/reduce_stream.ts @@ -32,7 +32,10 @@ import { Transform } from 'stream'; * initial value. * @return {Transform} */ -export function createReduceStream(reducer, initial) { +export function createReduceStream( + reducer: (value: any, chunk: T, enc: string) => T, + initial?: T +) { let i = -1; let value = initial; diff --git a/src/legacy/utils/streams/replace_stream.test.js b/src/core/server/utils/streams/replace_stream.test.ts similarity index 91% rename from src/legacy/utils/streams/replace_stream.test.js rename to src/core/server/utils/streams/replace_stream.test.ts index 01b89f93e5af0..c9da42395fb85 100644 --- a/src/legacy/utils/streams/replace_stream.test.js +++ b/src/core/server/utils/streams/replace_stream.test.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { Writable, Readable } from 'stream'; import { createReplaceStream, @@ -23,19 +24,19 @@ import { createPromiseFromStreams, createListStream, createMapStream, -} from './'; +} from './index'; -async function concatToString(streams) { +async function concatToString(streams: [Readable, ...Writable[]]) { return await createPromiseFromStreams([ ...streams, - createMapStream((buff) => buff.toString('utf8')), + createMapStream((buff: Buffer) => buff.toString('utf8')), createConcatStream(''), ]); } describe('replaceStream', () => { test('produces buffers when it receives buffers', async () => { - const chunks = await createPromiseFromStreams([ + const chunks = await createPromiseFromStreams([ createListStream([Buffer.from('foo'), Buffer.from('bar')]), createReplaceStream('o', '0'), createConcatStream([]), @@ -47,7 +48,7 @@ describe('replaceStream', () => { }); test('produces buffers when it receives strings', async () => { - const chunks = await createPromiseFromStreams([ + const chunks = await createPromiseFromStreams([ createListStream(['foo', 'bar']), createReplaceStream('o', '0'), createConcatStream([]), @@ -59,6 +60,7 @@ describe('replaceStream', () => { }); test('expects toReplace to be a string', () => { + // @ts-expect-error expect(() => createReplaceStream(Buffer.from('foo'))).toThrowError(/be a string/); }); diff --git a/src/legacy/utils/streams/replace_stream.js b/src/core/server/utils/streams/replace_stream.ts similarity index 96% rename from src/legacy/utils/streams/replace_stream.js rename to src/core/server/utils/streams/replace_stream.ts index 7309bd241fa52..05391bb3341c2 100644 --- a/src/legacy/utils/streams/replace_stream.js +++ b/src/core/server/utils/streams/replace_stream.ts @@ -19,7 +19,7 @@ import { Transform } from 'stream'; -export function createReplaceStream(toReplace, replacement) { +export function createReplaceStream(toReplace: string, replacement: string | Buffer) { if (typeof toReplace !== 'string') { throw new TypeError('toReplace must be a string'); } @@ -78,6 +78,7 @@ export function createReplaceStream(toReplace, replacement) { this.push(buffer); } + // @ts-expect-error buffer = null; callback(); }, diff --git a/src/legacy/utils/streams/split_stream.test.js b/src/core/server/utils/streams/split_stream.test.ts similarity index 93% rename from src/legacy/utils/streams/split_stream.test.js rename to src/core/server/utils/streams/split_stream.test.ts index e0736d220ba5c..f131bd0661e54 100644 --- a/src/legacy/utils/streams/split_stream.test.js +++ b/src/core/server/utils/streams/split_stream.test.ts @@ -16,16 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +import { Transform } from 'stream'; +import { createSplitStream, createConcatStream, createPromiseFromStreams } from './index'; -import { createSplitStream, createConcatStream, createPromiseFromStreams } from './'; - -async function split(stream, input) { +async function split(stream: Transform, input: Array) { const concat = createConcatStream(); concat.write([]); stream.pipe(concat); const output = createPromiseFromStreams([concat]); - input.forEach((i) => { + input.forEach((i: any) => { stream.write(i); }); stream.end(); diff --git a/src/legacy/utils/streams/split_stream.js b/src/core/server/utils/streams/split_stream.ts similarity index 95% rename from src/legacy/utils/streams/split_stream.js rename to src/core/server/utils/streams/split_stream.ts index f55cbc7bd290d..ae820f60abbf6 100644 --- a/src/legacy/utils/streams/split_stream.js +++ b/src/core/server/utils/streams/split_stream.ts @@ -38,7 +38,7 @@ import { Transform } from 'stream'; * @param {String} splitChunk * @return {Transform} */ -export function createSplitStream(splitChunk) { +export function createSplitStream(splitChunk: string | Uint8Array) { let unsplitBuffer = Buffer.alloc(0); return new Transform({ @@ -55,7 +55,7 @@ export function createSplitStream(splitChunk) { } unsplitBuffer = toSplit; - callback(null); + callback(); } catch (err) { callback(err); } @@ -65,7 +65,7 @@ export function createSplitStream(splitChunk) { try { this.push(unsplitBuffer.toString('utf8')); - callback(null); + callback(); } catch (err) { callback(err); } diff --git a/src/dev/build/lib/watch_stdio_for_line.ts b/src/dev/build/lib/watch_stdio_for_line.ts index 2322d017abc61..3d7929ccfc33a 100644 --- a/src/dev/build/lib/watch_stdio_for_line.ts +++ b/src/dev/build/lib/watch_stdio_for_line.ts @@ -24,7 +24,7 @@ import { createPromiseFromStreams, createSplitStream, createMapStream, -} from '../../../legacy/utils/streams'; +} from '../../../core/server/utils'; // creates a stream that skips empty lines unless they are followed by // another line, preventing the empty lines produced by splitStream diff --git a/src/legacy/server/i18n/index.ts b/src/legacy/server/i18n/index.ts index 09f7022436049..e895f83fe6901 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/legacy/server/i18n/index.ts @@ -20,7 +20,6 @@ import { i18n, i18nLoader } from '@kbn/i18n'; import { basename } from 'path'; import { Server } from 'hapi'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; import { getTranslationPaths } from './get_translations_path'; import { I18N_RC } from './constants'; diff --git a/src/legacy/server/logging/log_format_json.test.js b/src/legacy/server/logging/log_format_json.test.js index 31e622ecae611..f4fb939750566 100644 --- a/src/legacy/server/logging/log_format_json.test.js +++ b/src/legacy/server/logging/log_format_json.test.js @@ -21,7 +21,7 @@ import moment from 'moment'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { attachMetaData } from '../../../../src/core/server/legacy/logging/legacy_logging_server'; -import { createListStream, createPromiseFromStreams } from '../../utils'; +import { createListStream, createPromiseFromStreams } from '../../../core/server/utils'; import KbnLoggerJsonFormat from './log_format_json'; diff --git a/src/legacy/server/logging/log_format_string.test.js b/src/legacy/server/logging/log_format_string.test.js index 067ad70380961..842325865cce2 100644 --- a/src/legacy/server/logging/log_format_string.test.js +++ b/src/legacy/server/logging/log_format_string.test.js @@ -21,7 +21,7 @@ import moment from 'moment'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { attachMetaData } from '../../../../src/core/server/legacy/logging/legacy_logging_server'; -import { createListStream, createPromiseFromStreams } from '../../utils'; +import { createListStream, createPromiseFromStreams } from '../../../core/server/utils'; import KbnLoggerStringFormat from './log_format_string'; diff --git a/src/legacy/utils/artifact_type.ts b/src/legacy/utils/artifact_type.ts index 69f728e9e2220..ef471ef8e050d 100644 --- a/src/legacy/utils/artifact_type.ts +++ b/src/legacy/utils/artifact_type.ts @@ -17,7 +17,6 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { pkg } from '../../core/server/utils'; export const IS_KIBANA_DISTRIBUTABLE = pkg.build && pkg.build.distributable === true; export const IS_KIBANA_RELEASE = pkg.build && pkg.build.release === true; diff --git a/src/legacy/utils/index.d.ts b/src/legacy/utils/index.d.ts index c294c79542bbe..a57caad1d34bf 100644 --- a/src/legacy/utils/index.d.ts +++ b/src/legacy/utils/index.d.ts @@ -18,16 +18,3 @@ */ export function unset(object: object, rawPath: string): void; - -export { - concatStreamProviders, - createConcatStream, - createFilterStream, - createIntersperseStream, - createListStream, - createMapStream, - createPromiseFromStreams, - createReduceStream, - createReplaceStream, - createSplitStream, -} from './streams'; diff --git a/src/legacy/utils/index.js b/src/legacy/utils/index.js index 4274fb2e4901a..529b1ddfd8a4d 100644 --- a/src/legacy/utils/index.js +++ b/src/legacy/utils/index.js @@ -23,15 +23,3 @@ export { deepCloneWithBuffers } from './deep_clone_with_buffers'; export { unset } from './unset'; export { IS_KIBANA_DISTRIBUTABLE } from './artifact_type'; export { IS_KIBANA_RELEASE } from './artifact_type'; - -export { - concatStreamProviders, - createConcatStream, - createIntersperseStream, - createListStream, - createPromiseFromStreams, - createReduceStream, - createSplitStream, - createMapStream, - createReplaceStream, -} from './streams'; diff --git a/src/legacy/utils/streams/index.d.ts b/src/legacy/utils/streams/index.d.ts deleted file mode 100644 index 470b5d9fa3505..0000000000000 --- a/src/legacy/utils/streams/index.d.ts +++ /dev/null @@ -1,36 +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 { Readable, Writable, Transform, TransformOptions } from 'stream'; - -export function concatStreamProviders( - sourceProviders: Array<() => Readable>, - options: TransformOptions -): Transform; -export function createIntersperseStream(intersperseChunk: string | Buffer): Transform; -export function createSplitStream(splitChunk: T): Transform; -export function createListStream(items: any | any[]): Readable; -export function createReduceStream(reducer: (value: any, chunk: T, enc: string) => T): Transform; -export function createPromiseFromStreams([first, ...rest]: [Readable, ...Writable[]]): Promise< - T ->; -export function createConcatStream(initial?: any): Transform; -export function createMapStream(fn: (value: T, i: number) => void): Transform; -export function createReplaceStream(toReplace: string, replacement: string | Buffer): Transform; -export function createFilterStream(fn: (obj: T) => boolean): Transform; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 18eea7c45585f..8a7215e5a5bad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -20,7 +20,7 @@ import { importRulesSchema as importRulesResponseSchema, } from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; import { IRouter } from '../../../../../../../../src/core/server'; -import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; +import { createPromiseFromStreams } from '../../../../../../../../src/core/server/utils/'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { ConfigType } from '../../../../config'; import { SetupPlugins } from '../../../../plugin'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 3122db1919c3c..11f74c264ae0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -22,7 +22,7 @@ import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleTypeParams } from '../../types'; import { BulkError, ImportSuccessError } from '../utils'; import { getOutputRuleAlertForRest } from '../__mocks__/utils'; -import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; +import { createPromiseFromStreams } from '../../../../../../../../src/core/server/utils'; import { PartialAlert } from '../../../../../../alerts/server'; import { SanitizedAlert } from '../../../../../../alerts/server/types'; import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index f2061ce1d36de..60071bc2cef41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -5,7 +5,7 @@ */ import { Readable } from 'stream'; import { createRulesStreamFromNdJson } from './create_rules_stream_from_ndjson'; -import { createPromiseFromStreams } from 'src/legacy/utils/streams'; +import { createPromiseFromStreams } from 'src/core/server/utils'; import { BadRequestError } from '../errors/bad_request_error'; import { ImportRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index d7723232ca921..cd574a8d95615 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -19,7 +19,7 @@ import { createSplitStream, createMapStream, createConcatStream, -} from '../../../../../../../src/legacy/utils/streams'; +} from '../../../../../../../src/core/server/utils'; import { BadRequestError } from '../errors/bad_request_error'; import { parseNdjsonStrings, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts index 6b4017b5e4d5c..2827cd373d5e7 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/create_timelines_stream_from_ndjson.ts @@ -13,7 +13,7 @@ import { createConcatStream, createSplitStream, createMapStream, -} from '../../../../../../src/legacy/utils'; +} from '../../../../../../src/core/server/utils'; import { parseNdjsonStrings, filterExportedCounts, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts index ff76045db90cb..388ab5db3e852 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.test.ts @@ -79,7 +79,7 @@ describe('import timelines', () => { }; }); - jest.doMock('../../../../../../../src/legacy/utils', () => { + jest.doMock('../../../../../../../src/core/server/utils', () => { return { createPromiseFromStreams: jest.fn().mockReturnValue(mockParsedObjects), }; @@ -543,7 +543,7 @@ describe('import timeline templates', () => { }; }); - jest.doMock('../../../../../../../src/legacy/utils', () => { + jest.doMock('../../../../../../../src/core/server/utils', () => { return { createPromiseFromStreams: jest.fn().mockReturnValue(mockParsedTemplateTimelineObjects), }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts index fc25f1a48194e..9a3dbf365e026 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/common.ts @@ -10,7 +10,7 @@ import { Readable } from 'stream'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { createListStream } from '../../../../../../../../src/legacy/utils'; +import { createListStream } from '../../../../../../../../src/core/server/utils'; import { SetupPlugins } from '../../../../plugin'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts index f62f02cc7bba9..a19b18e7d89b1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/import_timelines.ts @@ -21,7 +21,7 @@ import { createBulkErrorObject, BulkError } from '../../../detection_engine/rout import { createTimelines } from './create_timelines'; import { FrameworkRequest } from '../../../framework'; import { createTimelinesStreamFromNdJson } from '../../create_timelines_stream_from_ndjson'; -import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils'; +import { createPromiseFromStreams } from '../../../../../../../../src/core/server/utils'; import { getTupleDuplicateErrorsAndUniqueTimeline } from './get_timelines_from_stream'; import { CompareTimelinesStatus } from './compare_timelines_status'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts index 66f16db01a508..c63978a1f046e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/install_prepacked_timelines.test.ts @@ -5,7 +5,7 @@ */ import { join, resolve } from 'path'; -import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; +import { createPromiseFromStreams } from '../../../../../../../../src/core/server/utils'; import { SecurityPluginSetup } from '../../../../../../security/server'; import { FrameworkRequest } from '../../../framework'; diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index 0eb021bfe2a83..4446e82f99de4 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -16,7 +16,7 @@ import { ImportRulesSchema, } from '../../../common/detection_engine/schemas/request/import_rules_schema'; import { exactCheck } from '../../../common/exact_check'; -import { createMapStream, createFilterStream } from '../../../../../../src/legacy/utils/streams'; +import { createMapStream, createFilterStream } from '../../../../../../src/core/server/utils'; import { BadRequestError } from '../../lib/detection_engine/errors/bad_request_error'; export interface RulesObjectsExportResultDetails { diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_copy_to_space_mocks.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_copy_to_space_mocks.ts index 0e117b3f16e3f..ef6f5e1541a46 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_copy_to_space_mocks.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_copy_to_space_mocks.ts @@ -5,7 +5,7 @@ */ import { Readable } from 'stream'; -import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils'; +import { createPromiseFromStreams, createConcatStream } from 'src/core/server/utils'; async function readStreamToCompletion(stream: Readable) { return (await (createPromiseFromStreams([stream, createConcatStream([])]) as unknown)) as any[]; From 411048ae0963c2193f753552f37dcda6277d1c5b Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 3 Sep 2020 12:30:31 +0300 Subject: [PATCH 27/43] Replace FetchOptions with ISearchOptions (#76538) * Replace FetchOptions with ISearchOptions and update usage * Merge the client and server ISearchOption types into common * docs --- ...ns-data-public.fetchoptions.abortsignal.md | 11 -------- ...plugin-plugins-data-public.fetchoptions.md | 19 -------------- ...ta-public.fetchoptions.searchstrategyid.md | 11 -------- ...-data-public.isearchoptions.abortsignal.md | 13 ++++++++++ ...ugin-plugins-data-public.isearchoptions.md | 4 +-- ...ugins-data-public.isearchoptions.signal.md | 11 -------- ...ins-data-public.isearchoptions.strategy.md | 2 ++ .../kibana-plugin-plugins-data-public.md | 1 - ...data-server.isearchoptions.abortsignal.md} | 6 ++--- ...ugin-plugins-data-server.isearchoptions.md | 4 +-- ...ins-data-server.isearchoptions.strategy.md | 2 ++ .../data/common/search/aggs/agg_config.ts | 9 ++++--- .../data/common/search/aggs/agg_configs.ts | 4 +-- .../common/search/aggs/param_types/base.ts | 4 +-- .../data/common/search/es_search/index.ts | 1 + .../data/common/search/es_search/types.ts | 11 ++++++++ src/plugins/data/common/search/index.ts | 1 + src/plugins/data/public/index.ts | 4 +-- src/plugins/data/public/public.api.md | 16 ++---------- .../data/public/search/expressions/esdsl.ts | 2 +- src/plugins/data/public/search/fetch/types.ts | 5 ---- src/plugins/data/public/search/index.ts | 11 ++------ .../data/public/search/legacy/call_client.ts | 9 ++++--- .../public/search/legacy/fetch_soon.test.ts | 6 ++--- .../data/public/search/legacy/fetch_soon.ts | 10 ++++---- .../public/search/search_interceptor.test.ts | 6 +++-- .../data/public/search/search_interceptor.ts | 12 ++++++--- .../search/search_source/search_source.ts | 25 +++++++++++-------- src/plugins/data/public/search/types.ts | 6 +---- src/plugins/data/server/index.ts | 2 +- src/plugins/data/server/search/index.ts | 8 +----- src/plugins/data/server/search/routes.ts | 4 +-- .../data/server/search/search_service.ts | 12 +++++---- src/plugins/data/server/search/types.ts | 10 +------- src/plugins/data/server/server.api.md | 5 ++-- .../public/data_model/search_api.ts | 2 +- .../public/search/search_interceptor.test.ts | 4 +-- .../server/search/es_search_strategy.ts | 3 +-- .../hosts/first_last_seen/index.tsx | 2 +- .../public/hosts/containers/hosts/index.tsx | 2 +- .../containers/hosts/overview/_index.tsx | 2 +- .../network/containers/network_http/index.tsx | 2 +- .../public/network/containers/tls/index.tsx | 2 +- 43 files changed, 117 insertions(+), 169 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md rename docs/development/plugins/data/server/{kibana-plugin-plugins-data-server.isearchoptions.signal.md => kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md} (62%) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md deleted file mode 100644 index 791f1b63e6539..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md) - -## FetchOptions.abortSignal property - -Signature: - -```typescript -abortSignal?: AbortSignal; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md deleted file mode 100644 index f07fdd4280533..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) - -## FetchOptions interface - -Signature: - -```typescript -export interface FetchOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [abortSignal](./kibana-plugin-plugins-data-public.fetchoptions.abortsignal.md) | AbortSignal | | -| [searchStrategyId](./kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md) | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md deleted file mode 100644 index 8824529eb4eca..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) > [searchStrategyId](./kibana-plugin-plugins-data-public.fetchoptions.searchstrategyid.md) - -## FetchOptions.searchStrategyId property - -Signature: - -```typescript -searchStrategyId?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md new file mode 100644 index 0000000000000..fd8d322d54b26 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) + +## ISearchOptions.abortSignal property + +An `AbortSignal` that allows the caller of `search` to abort a search request. + +Signature: + +```typescript +abortSignal?: AbortSignal; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 3eb38dc7d52e0..c9018b0048aa3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -14,6 +14,6 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | -| [signal](./kibana-plugin-plugins-data-public.isearchoptions.signal.md) | AbortSignal | | -| [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | | +| [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md deleted file mode 100644 index 10bd186d55baa..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.signal.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [signal](./kibana-plugin-plugins-data-public.isearchoptions.signal.md) - -## ISearchOptions.signal property - -Signature: - -```typescript -signal?: AbortSignal; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md index df7e050691a8f..bd2580957f6c1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md @@ -4,6 +4,8 @@ ## ISearchOptions.strategy property +Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 0ab86e0f4dab2..b651480a85899 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -50,7 +50,6 @@ | [DataPublicPluginSetup](./kibana-plugin-plugins-data-public.datapublicpluginsetup.md) | | | [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | | | [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | -| [FetchOptions](./kibana-plugin-plugins-data-public.fetchoptions.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [FieldMappingSpec](./kibana-plugin-plugins-data-public.fieldmappingspec.md) | | | [Filter](./kibana-plugin-plugins-data-public.filter.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md similarity index 62% rename from docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md index 948dfd66da7a0..693345f480a9a 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.signal.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) -## ISearchOptions.signal property +## ISearchOptions.abortSignal property An `AbortSignal` that allows the caller of `search` to abort a search request. Signature: ```typescript -signal?: AbortSignal; +abortSignal?: AbortSignal; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index 002ce864a1aa4..21ddaef3a0b94 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -14,6 +14,6 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | -| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | -| [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | | +| [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md index 6df72d023e2c0..65da7fddd13f6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.strategy.md @@ -4,6 +4,8 @@ ## ISearchOptions.strategy property +Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + Signature: ```typescript diff --git a/src/plugins/data/common/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts index b5747ce7bb9bd..201e9f1ec402c 100644 --- a/src/plugins/data/common/search/aggs/agg_config.ts +++ b/src/plugins/data/common/search/aggs/agg_config.ts @@ -21,12 +21,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { Assign, Ensure } from '@kbn/utility-types'; -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchSource } from 'src/plugins/data/public'; import { ExpressionAstFunction, ExpressionAstArgument, SerializedFieldFormat, } from 'src/plugins/expressions/common'; +import { ISearchOptions } from '../es_search'; import { IAggType } from './agg_type'; import { writeParams } from './agg_params'; @@ -213,11 +214,11 @@ export class AggConfig { /** * Hook for pre-flight logic, see AggType#onSearchRequestStart - * @param {Courier.SearchSource} searchSource - * @param {Courier.FetchOptions} options + * @param {SearchSource} searchSource + * @param {ISearchOptions} options * @return {Promise} */ - onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) { + onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions) { if (!this.type) { return Promise.resolve(); } diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index 203eda3a907ee..282e6f3b538a4 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import { Assign } from '@kbn/utility-types'; -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; import { AggConfig, AggConfigSerialized, IAggConfig } from './agg_config'; import { IAggType } from './agg_type'; import { AggTypesRegistryStart } from './agg_types_registry'; @@ -300,7 +300,7 @@ export class AggConfigs { return _.find(reqAgg.getResponseAggs(), { id }); } - onSearchRequestStart(searchSource: ISearchSource, options?: FetchOptions) { + onSearchRequestStart(searchSource: ISearchSource, options?: ISearchOptions) { return Promise.all( // @ts-ignore this.getRequestAggs().map((agg: AggConfig) => agg.onSearchRequestStart(searchSource, options)) diff --git a/src/plugins/data/common/search/aggs/param_types/base.ts b/src/plugins/data/common/search/aggs/param_types/base.ts index 3a12a9a54500f..c0316c974e26f 100644 --- a/src/plugins/data/common/search/aggs/param_types/base.ts +++ b/src/plugins/data/common/search/aggs/param_types/base.ts @@ -17,7 +17,7 @@ * under the License. */ -import { FetchOptions, ISearchSource } from 'src/plugins/data/public'; +import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { IAggConfigs } from '../agg_configs'; import { IAggConfig } from '../agg_config'; @@ -56,7 +56,7 @@ export class BaseParamType { modifyAggConfigOnSearchRequestStart: ( aggConfig: TAggConfig, searchSource?: ISearchSource, - options?: FetchOptions + options?: ISearchOptions ) => void; constructor(config: Record) { diff --git a/src/plugins/data/common/search/es_search/index.ts b/src/plugins/data/common/search/es_search/index.ts index 7bc9cada8f0ee..54757b53b8665 100644 --- a/src/plugins/data/common/search/es_search/index.ts +++ b/src/plugins/data/common/search/es_search/index.ts @@ -22,4 +22,5 @@ export { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY, + ISearchOptions, } from './types'; diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 3184fbe341705..89faa5b7119c8 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -22,6 +22,17 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; export const ES_SEARCH_STRATEGY = 'es'; +export interface ISearchOptions { + /** + * An `AbortSignal` that allows the caller of `search` to abort a search request. + */ + abortSignal?: AbortSignal; + /** + * Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + */ + strategy?: string; +} + export type ISearchRequestParams> = { trackTotalHits?: boolean; } & Search; diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index d8184551b7f3d..3bfb0ddb89aa9 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -28,4 +28,5 @@ export { IEsSearchResponse, ES_SEARCH_STRATEGY, ISearchRequestParams, + ISearchOptions, } from './es_search'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index a9714a95ff338..f7b4111df5172 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -342,7 +342,6 @@ export { ES_SEARCH_STRATEGY, EsQuerySortValue, extractSearchSourceReferences, - FetchOptions, getEsPreference, getSearchParamsFromRequest, IEsSearchRequest, @@ -352,7 +351,6 @@ export { injectSearchSourceReferences, ISearch, ISearchGeneric, - ISearchOptions, ISearchSource, parseSearchSourceJSON, RequestTimeoutError, @@ -367,6 +365,8 @@ export { EsRawResponseExpressionTypeDefinition, } from './search'; +export { ISearchOptions } from '../common'; + // Search namespace export const search = { aggs: { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e72fea160cc28..9f727d86b06e1 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -26,11 +26,11 @@ import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsSetup } from 'src/plugins/expressions/public'; -import { FetchOptions as FetchOptions_2 } from 'src/plugins/data/public'; import { History } from 'history'; import { Href } from 'history'; import { IconType } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n/react'; +import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource as ISearchSource_2 } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiSettingsClient } from 'src/core/public'; @@ -486,16 +486,6 @@ export const extractSearchSourceReferences: (state: SearchSourceFields) => [Sear indexRefName?: string; }, SavedObjectReference[]]; -// Warning: (ae-missing-release-tag) "FetchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface FetchOptions { - // (undocumented) - abortSignal?: AbortSignal; - // (undocumented) - searchStrategyId?: string; -} - // Warning: (ae-missing-release-tag) "FieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1233,9 +1223,7 @@ export type ISearchGeneric = ({ body: dsl, }, }, - { signal: abortSignal } + { abortSignal } ) .toPromise(); diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts index 670c4f731971a..81146c6b74c05 100644 --- a/src/plugins/data/public/search/fetch/types.ts +++ b/src/plugins/data/public/search/fetch/types.ts @@ -29,11 +29,6 @@ import { ISearchStartLegacy } from '../types'; */ export type SearchRequest = Record; -export interface FetchOptions { - abortSignal?: AbortSignal; - searchStrategyId?: string; -} - export interface FetchHandlers { legacySearchService: ISearchStartLegacy; config: { get: GetConfigFn }; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 14eff13b378ee..a6a1736ac91da 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -19,14 +19,7 @@ export * from './expressions'; -export { - ISearch, - ISearchOptions, - ISearchGeneric, - ISearchSetup, - ISearchStart, - SearchEnhancements, -} from './types'; +export { ISearch, ISearchGeneric, ISearchSetup, ISearchStart, SearchEnhancements } from './types'; export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search'; @@ -34,7 +27,7 @@ export { getEsPreference } from './es_search'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; -export { SearchError, FetchOptions, getSearchParamsFromRequest, SearchRequest } from './fetch'; +export { SearchError, getSearchParamsFromRequest, SearchRequest } from './fetch'; export { ISearchSource, diff --git a/src/plugins/data/public/search/legacy/call_client.ts b/src/plugins/data/public/search/legacy/call_client.ts index 3dcf11f72a742..d66796b9427a1 100644 --- a/src/plugins/data/public/search/legacy/call_client.ts +++ b/src/plugins/data/public/search/legacy/call_client.ts @@ -18,21 +18,22 @@ */ import { SearchResponse } from 'elasticsearch'; -import { FetchOptions, FetchHandlers, handleResponse } from '../fetch'; +import { ISearchOptions } from 'src/plugins/data/common'; +import { FetchHandlers, handleResponse } from '../fetch'; import { defaultSearchStrategy } from './default_search_strategy'; import { SearchRequest } from '../index'; export function callClient( searchRequests: SearchRequest[], - requestsOptions: FetchOptions[] = [], + requestsOptions: ISearchOptions[] = [], fetchHandlers: FetchHandlers ) { // Correlate the options with the request that they're associated with const requestOptionEntries: Array<[ SearchRequest, - FetchOptions + ISearchOptions ]> = searchRequests.map((request, i) => [request, requestsOptions[i]]); - const requestOptionsMap = new Map(requestOptionEntries); + const requestOptionsMap = new Map(requestOptionEntries); const requestResponseMap = new Map>>(); const { searching, abort } = defaultSearchStrategy.search({ diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts index d7a85e65b475d..d38a41cf5ffbc 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts @@ -19,10 +19,10 @@ import { fetchSoon } from './fetch_soon'; import { callClient } from './call_client'; -import { FetchHandlers, FetchOptions } from '../fetch/types'; +import { FetchHandlers } from '../fetch/types'; import { SearchRequest } from '../index'; import { SearchResponse } from 'elasticsearch'; -import { GetConfigFn, UI_SETTINGS } from '../../../common'; +import { GetConfigFn, UI_SETTINGS, ISearchOptions } from '../../../common'; function getConfigStub(config: any = {}): GetConfigFn { return (key) => config[key]; @@ -102,7 +102,7 @@ describe('fetchSoon', () => { const options = [{ bar: 1 }, { bar: 2 }]; requests.forEach((request, i) => { - fetchSoon(request, options[i] as FetchOptions, { config } as FetchHandlers); + fetchSoon(request, options[i] as ISearchOptions, { config } as FetchHandlers); }); jest.advanceTimersByTime(50); diff --git a/src/plugins/data/public/search/legacy/fetch_soon.ts b/src/plugins/data/public/search/legacy/fetch_soon.ts index 16920a8a4dd97..37c3827bb7bba 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.ts @@ -19,9 +19,9 @@ import { SearchResponse } from 'elasticsearch'; import { callClient } from './call_client'; -import { FetchHandlers, FetchOptions } from '../fetch/types'; +import { FetchHandlers } from '../fetch/types'; import { SearchRequest } from '../index'; -import { UI_SETTINGS } from '../../../common'; +import { UI_SETTINGS, ISearchOptions } from '../../../common'; /** * This function introduces a slight delay in the request process to allow multiple requests to queue @@ -29,7 +29,7 @@ import { UI_SETTINGS } from '../../../common'; */ export async function fetchSoon( request: SearchRequest, - options: FetchOptions, + options: ISearchOptions, fetchHandlers: FetchHandlers ) { const msToDelay = fetchHandlers.config.get(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0; @@ -51,7 +51,7 @@ function delay(fn: (...args: any) => T, ms: number): Promise { // The current batch/queue of requests to fetch let requestsToFetch: SearchRequest[] = []; -let requestOptions: FetchOptions[] = []; +let requestOptions: ISearchOptions[] = []; // The in-progress fetch (if there is one) let fetchInProgress: any = null; @@ -65,7 +65,7 @@ let fetchInProgress: any = null; */ async function delayedFetch( request: SearchRequest, - options: FetchOptions, + options: ISearchOptions, fetchHandlers: FetchHandlers, ms: number ): Promise> { diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index 2eded17bda88c..da60f39b522ac 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -112,7 +112,9 @@ describe('SearchInterceptor', () => { const mockRequest: IEsSearchRequest = { params: {}, }; - const response = searchInterceptor.search(mockRequest, { signal: abortController.signal }); + const response = searchInterceptor.search(mockRequest, { + abortSignal: abortController.signal, + }); const next = jest.fn(); const error = (e: any) => { @@ -131,7 +133,7 @@ describe('SearchInterceptor', () => { const mockRequest: IEsSearchRequest = { params: {}, }; - const response = searchInterceptor.search(mockRequest, { signal: abort.signal }); + const response = searchInterceptor.search(mockRequest, { abortSignal: abort.signal }); abort.abort(); const error = (e: any) => { diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 30e509edd4987..c6c03267163c9 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -22,8 +22,12 @@ import { BehaviorSubject, throwError, timer, Subscription, defer, from, Observab import { finalize, filter } from 'rxjs/operators'; import { Toast, CoreStart, ToastsSetup, CoreSetup } from 'kibana/public'; import { getCombinedSignal, AbortError } from '../../common/utils'; -import { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY } from '../../common/search'; -import { ISearchOptions } from './types'; +import { + IEsSearchRequest, + IEsSearchResponse, + ISearchOptions, + ES_SEARCH_STRATEGY, +} from '../../common/search'; import { getLongQueryNotification } from './long_query_notification'; import { SearchUsageCollector } from './collectors'; @@ -128,7 +132,7 @@ export class SearchInterceptor { ): Observable { // Defer the following logic until `subscribe` is actually called return defer(() => { - if (options?.signal?.aborted) { + if (options?.abortSignal?.aborted) { return throwError(new AbortError()); } @@ -164,7 +168,7 @@ export class SearchInterceptor { const signals = [ this.abortController.signal, timeoutSignal, - ...(options?.signal ? [options.signal] : []), + ...(options?.abortSignal ? [options.abortSignal] : []), ]; const combinedSignal = getCombinedSignal(signals); diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index d2e3370762059..3a567501a7540 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -78,14 +78,19 @@ import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern, ISearchGeneric } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { - FetchOptions, RequestFailure, handleResponse, getSearchParamsFromRequest, SearchRequest, } from '../fetch'; -import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; +import { + getEsQueryConfig, + buildEsQuery, + Filter, + UI_SETTINGS, + ISearchOptions, +} from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { GetConfigFn } from '../../../common/types'; import { fetchSoon } from '../legacy'; @@ -121,7 +126,7 @@ export class SearchSource { private searchStrategyId?: string; private parent?: SearchSource; private requestStartHandlers: Array< - (searchSource: SearchSource, options?: FetchOptions) => Promise + (searchSource: SearchSource, options?: ISearchOptions) => Promise > = []; private inheritOptions: SearchSourceOptions = {}; public history: SearchRequest[] = []; @@ -225,7 +230,7 @@ export class SearchSource { * Run a search using the search service * @return {Observable>} */ - private fetch$(searchRequest: SearchRequest, signal?: AbortSignal) { + private fetch$(searchRequest: SearchRequest, options: ISearchOptions) { const { search, esShardTimeout, getConfig } = this.dependencies; const params = getSearchParamsFromRequest(searchRequest, { @@ -233,7 +238,7 @@ export class SearchSource { getConfig, }); - return search({ params, indexType: searchRequest.indexType }, { signal }).pipe( + return search({ params, indexType: searchRequest.indexType }, options).pipe( map(({ rawResponse }) => handleResponse(searchRequest, rawResponse)) ); } @@ -242,7 +247,7 @@ export class SearchSource { * Run a search using the search service * @return {Promise>} */ - private async legacyFetch(searchRequest: SearchRequest, options: FetchOptions) { + private async legacyFetch(searchRequest: SearchRequest, options: ISearchOptions) { const { esShardTimeout, legacySearch, getConfig } = this.dependencies; return await fetchSoon( @@ -263,7 +268,7 @@ export class SearchSource { * * @async */ - async fetch(options: FetchOptions = {}) { + async fetch(options: ISearchOptions = {}) { const { getConfig } = this.dependencies; await this.requestIsStarting(options); @@ -274,7 +279,7 @@ export class SearchSource { if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { response = await this.legacyFetch(searchRequest, options); } else { - response = await this.fetch$(searchRequest, options.abortSignal).toPromise(); + response = await this.fetch$(searchRequest, options).toPromise(); } // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved @@ -291,7 +296,7 @@ export class SearchSource { * @return {undefined} */ onRequestStart( - handler: (searchSource: SearchSource, options?: FetchOptions) => Promise + handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise ) { this.requestStartHandlers.push(handler); } @@ -318,7 +323,7 @@ export class SearchSource { * @param options * @return {Promise} */ - private requestIsStarting(options: FetchOptions = {}) { + private requestIsStarting(options: ISearchOptions = {}) { const handlers = [...this.requestStartHandlers]; // If callParentStartHandlers has been set to true, we also call all // handlers of parent search sources. diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 55726e40f5a77..b0ac730d8afb1 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -29,15 +29,11 @@ import { IKibanaSearchResponse, IEsSearchRequest, IEsSearchResponse, + ISearchOptions, } from '../../common/search'; import { IndexPatternsContract } from '../../common/index_patterns/index_patterns'; import { UsageCollectionSetup } from '../../../usage_collection/public'; -export interface ISearchOptions { - signal?: AbortSignal; - strategy?: string; -} - export type ISearch = ( request: IKibanaSearchRequest, options?: ISearchOptions diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index c3b06992dba0e..f300fb0779e38 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -198,6 +198,7 @@ export { OptionedValueProp, ParsedInterval, // search + ISearchOptions, IEsSearchRequest, IEsSearchResponse, // tabify @@ -208,7 +209,6 @@ export { export { ISearchStrategy, - ISearchOptions, ISearchSetup, ISearchStart, getDefaultSearchParams, diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 02c21c3254645..8a74c51f52f51 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -17,13 +17,7 @@ * under the License. */ -export { - ISearchStrategy, - ISearchOptions, - ISearchSetup, - ISearchStart, - SearchEnhancements, -} from './types'; +export { ISearchStrategy, ISearchSetup, ISearchStart, SearchEnhancements } from './types'; export { getDefaultSearchParams, getTotalLoaded } from './es_search'; diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 3d813f745305f..be5c8d035edff 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -42,7 +42,7 @@ export function registerSearchRoute(core: CoreSetup): v async (context, request, res) => { const searchRequest = request.body; const { strategy, id } = request.params; - const signal = getRequestAbortedSignal(request.events.aborted$); + const abortSignal = getRequestAbortedSignal(request.events.aborted$); const [, , selfStart] = await core.getStartServices(); @@ -51,7 +51,7 @@ export function registerSearchRoute(core: CoreSetup): v context, { ...searchRequest, id }, { - signal, + abortSignal, strategy, } ); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index edc94961c79d8..da14995af1fa4 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -37,7 +37,7 @@ import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; -import { IEsSearchRequest, IEsSearchResponse } from '../../common'; +import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../common'; type StrategyMap< SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, @@ -102,11 +102,13 @@ export class SearchService implements Plugin { private search( context: RequestHandlerContext, searchRequest: IEsSearchRequest, - options: Record + options: ISearchOptions ) { - return this.getSearchStrategy( - options.strategy || this.defaultSearchStrategyName - ).search(context, searchRequest, { signal: options.signal }); + return this.getSearchStrategy(options.strategy || this.defaultSearchStrategyName).search( + context, + searchRequest, + options + ); } public start( diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 5ce1bb3e6b9f8..6ce8430d0573b 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -18,7 +18,7 @@ */ import { RequestHandlerContext } from '../../../../core/server'; -import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; +import { IKibanaSearchResponse, IKibanaSearchRequest, ISearchOptions } from '../../common/search'; import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors/usage'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; @@ -27,14 +27,6 @@ export interface SearchEnhancements { defaultStrategy: string; } -export interface ISearchOptions { - /** - * An `AbortSignal` that allows the caller of `search` to abort a search request. - */ - signal?: AbortSignal; - strategy?: string; -} - export interface ISearchSetup { aggs: AggsSetup; /** diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 9497a41b45ff9..93f924493c3b4 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -48,7 +48,6 @@ import { ExistsParams } from 'elasticsearch'; import { ExplainParams } from 'elasticsearch'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; -import { FetchOptions } from 'src/plugins/data/public'; import { FieldStatsParams } from 'elasticsearch'; import { GenericParams } from 'elasticsearch'; import { GetParams } from 'elasticsearch'; @@ -99,6 +98,7 @@ import { IngestDeletePipelineParams } from 'elasticsearch'; import { IngestGetPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch'; +import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; @@ -678,8 +678,7 @@ export class IndexPatternsFetcher { // // @public (undocumented) export interface ISearchOptions { - signal?: AbortSignal; - // (undocumented) + abortSignal?: AbortSignal; strategy?: string; } diff --git a/src/plugins/vis_type_vega/public/data_model/search_api.ts b/src/plugins/vis_type_vega/public/data_model/search_api.ts index d2ce8c95b9f90..8a1541ecae0d4 100644 --- a/src/plugins/vis_type_vega/public/data_model/search_api.ts +++ b/src/plugins/vis_type_vega/public/data_model/search_api.ts @@ -62,7 +62,7 @@ export class SearchAPI { requestResponders[requestId].json(params.body); } - return search({ params }, { signal: this.abortSignal }).pipe( + return search({ params }, { abortSignal: this.abortSignal }).pipe( tap((data) => this.inspectSearchResult(data, requestResponders[requestId])), map((data) => ({ name: requestId, diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index fe954f1602cd3..1e2c7987b7041 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -189,7 +189,7 @@ describe('EnhancedSearchInterceptor', () => { const abortController = new AbortController(); abortController.abort(); - const response = searchInterceptor.search({}, { signal: abortController.signal }); + const response = searchInterceptor.search({}, { abortSignal: abortController.signal }); response.subscribe({ next, error }); await timeTravel(500); @@ -225,7 +225,7 @@ describe('EnhancedSearchInterceptor', () => { const response = searchInterceptor.search( {}, - { signal: abortController.signal, pollInterval: 0 } + { abortSignal: abortController.signal, pollInterval: 0 } ); response.subscribe({ next, error }); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 46609af52d072..67a42b9954c9d 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -19,11 +19,10 @@ import { getTotalLoaded, ISearchStrategy, SearchUsage, - ISearchOptions, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; import { shimHitsTotal } from './shim_hits_total'; -import { IEsSearchResponse } from '../../../../../src/plugins/data/common/search/es_search'; +import { ISearchOptions, IEsSearchResponse } from '../../../../../src/plugins/data/common/search'; function isEnhancedEsSearchResponse(response: any): response is IEsSearchResponse { return response.hasOwnProperty('isPartial') && response.hasOwnProperty('isRunning'); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 3a93b1ee46e7b..169fe58e9a2cc 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -68,7 +68,7 @@ export const useFirstLastSeenHost = ({ const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionSearchStrategy', - signal: abortCtrl.current.signal, + abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index bc711d08065e2..c545a7a75457b 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -131,7 +131,7 @@ export const useAllHost = ({ const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionSearchStrategy', - signal: abortCtrl.current.signal, + abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx index a72ef0ff1c46e..b28f479634d42 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/overview/_index.tsx @@ -89,7 +89,7 @@ export const useHostOverview = ({ const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionSearchStrategy', - signal: abortCtrl.current.signal, + abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx index d71953eec026f..857d7fe0229b2 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx @@ -126,7 +126,7 @@ export const useNetworkHttp = ({ const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionSearchStrategy', - signal: abortCtrl.current.signal, + abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 1c2604ee65552..df02acf208603 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -125,7 +125,7 @@ export const useNetworkTls = ({ const searchSubscription$ = data.search .search(request, { strategy: 'securitySolutionSearchStrategy', - signal: abortCtrl.current.signal, + abortSignal: abortCtrl.current.signal, }) .subscribe({ next: (response) => { From 7134cd7b7e4fb6b43727df1e6fc5681666baca88 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 3 Sep 2020 11:36:27 +0200 Subject: [PATCH 28/43] [Discover] Fix sidebar element focus behavior when adding / removing columns (#75749) * Use field.name instead of idx for key in li element * onClick adds focus to button * prevent Chrome jumping back to last focused element --- .../public/application/angular/discover.html | 9 ++++---- .../components/sidebar/discover_field.tsx | 6 +++++ .../components/sidebar/discover_sidebar.tsx | 22 +++++++++---------- .../sidebar/discover_sidebar_directive.ts | 1 - .../public/field_button/field_button.tsx | 11 +++++++++- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/plugins/discover/public/application/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html index d3d4f524873d8..94f13e1cd8132 100644 --- a/src/plugins/discover/public/application/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -31,7 +31,6 @@

{{screenTitle}}

on-remove-field="removeColumn" selected-index-pattern="searchSource.getField('index')" set-index-pattern="setIndexPattern" - state="state" >
@@ -78,11 +77,11 @@

{{screenTitle}}

class="dscTimechart" ng-if="opts.timefield" > - ) => { + if (ev.type === 'click') { + ev.currentTarget.focus(); + } ev.preventDefault(); ev.stopPropagation(); toggleDisplay(field); @@ -155,6 +158,9 @@ export function DiscoverField({ iconType="cross" className="dscSidebarItem__action" onClick={(ev: React.MouseEvent) => { + if (ev.type === 'click') { + ev.currentTarget.focus(); + } ev.preventDefault(); ev.stopPropagation(); toggleDisplay(field); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 361c0707fef6b..850624888b24a 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -30,7 +30,6 @@ import { SavedObject } from '../../../../../../core/types'; import { FIELDS_LIMIT_SETTING } from '../../../../common'; import { groupFields } from './lib/group_fields'; import { IndexPatternField, IndexPattern, UI_SETTINGS } from '../../../../../data/public'; -import { AppState } from '../../angular/discover_state'; import { getDetails } from './lib/get_details'; import { getDefaultFieldFilter, setFieldFilterProp } from './lib/field_filter'; import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list'; @@ -74,10 +73,6 @@ export interface DiscoverSidebarProps { * Callback function to select another index pattern */ setIndexPattern: (id: string) => void; - /** - * Current app state, used for generating a link to visualize - */ - state: AppState; } export function DiscoverSidebar({ @@ -90,7 +85,6 @@ export function DiscoverSidebar({ onRemoveField, selectedIndexPattern, setIndexPattern, - state, }: DiscoverSidebarProps) { const [showFields, setShowFields] = useState(false); const [fields, setFields] = useState(null); @@ -185,10 +179,10 @@ export function DiscoverSidebar({ aria-labelledby="selected_fields" data-test-subj={`fieldList-selected`} > - {selectedFields.map((field: IndexPatternField, idx: number) => { + {selectedFields.map((field: IndexPatternField) => { return (
  • @@ -260,10 +254,10 @@ export function DiscoverSidebar({ aria-labelledby="available_fields available_fields_popular" data-test-subj={`fieldList-popular`} > - {popularFields.map((field: IndexPatternField, idx: number) => { + {popularFields.map((field: IndexPatternField) => { return (
  • @@ -290,9 +284,13 @@ export function DiscoverSidebar({ aria-labelledby="available_fields" data-test-subj={`fieldList-unpopular`} > - {unpopularFields.map((field: IndexPatternField, idx: number) => { + {unpopularFields.map((field: IndexPatternField) => { return ( -
  • +
  • -