From 99d3f8b7682320c74e565e2a7682eac8707e1186 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 28 Jun 2021 10:10:36 -0700 Subject: [PATCH 01/74] [kbn/optimizer] fix optimizerCache creation (#103190) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../src/optimizer/cache_keys.test.ts | 1 - .../kbn-optimizer/src/optimizer/cache_keys.ts | 36 ++++++------------- .../src/optimizer/get_changes.test.ts | 22 ++++++------ .../src/optimizer/get_changes.ts | 15 ++++---- 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts index 832fd812d36bb..335a4fd7f74c3 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts @@ -79,7 +79,6 @@ describe('getOptimizerCacheKey()', () => { await expect(getOptimizerCacheKey(config)).resolves.toMatchInlineSnapshot(` Object { - "bootstrap": "", "deletedPaths": Array [ "/foo/bar/c", ], diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.ts index e622b3b8f593e..da06b76327a8b 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.ts @@ -8,7 +8,6 @@ import Path from 'path'; import Fs from 'fs'; -import { promisify } from 'util'; import Chalk from 'chalk'; import execa from 'execa'; @@ -23,8 +22,7 @@ import { getMtimes } from './get_mtimes'; import { getChanges } from './get_changes'; import { OptimizerConfig } from './optimizer_config'; -const OPTIMIZER_DIR = Path.dirname(require.resolve('../../package.json')); -const RELATIVE_DIR = Path.relative(REPO_ROOT, OPTIMIZER_DIR); +const RELATIVE_DIR = 'packages/kbn-optimizer'; export function diffCacheKey(expected?: unknown, actual?: unknown) { const expectedJson = jsonStable(expected, { @@ -114,17 +112,12 @@ export function reformatJestDiff(diff: string | null) { export interface OptimizerCacheKey { readonly lastCommit: string | undefined; - readonly bootstrap: string | undefined; readonly workerConfig: CacheableWorkerConfig; readonly deletedPaths: string[]; readonly modifiedTimes: Record; } async function getLastCommit() { - if (!Fs.existsSync(Path.join(REPO_ROOT, '.git'))) { - return undefined; - } - const { stdout } = await execa( 'git', ['log', '-n', '1', '--pretty=format:%H', '--', RELATIVE_DIR], @@ -136,25 +129,19 @@ async function getLastCommit() { return stdout.trim() || undefined; } -async function getBootstrapCacheKey() { - try { - return await promisify(Fs.readFile)( - Path.resolve(OPTIMIZER_DIR, 'target/.bootstrap-cache'), - 'utf8' - ); - } catch (error) { - if (error?.code !== 'ENOENT') { - throw error; - } - return undefined; +export async function getOptimizerCacheKey(config: OptimizerConfig): Promise { + if (!Fs.existsSync(Path.resolve(REPO_ROOT, '.git'))) { + return { + lastCommit: undefined, + modifiedTimes: {}, + workerConfig: config.getCacheableWorkerConfig(), + deletedPaths: [], + }; } -} -export async function getOptimizerCacheKey(config: OptimizerConfig) { - const [changes, lastCommit, bootstrap] = await Promise.all([ - getChanges(OPTIMIZER_DIR), + const [changes, lastCommit] = await Promise.all([ + getChanges(RELATIVE_DIR), getLastCommit(), - getBootstrapCacheKey(), ] as const); const deletedPaths: string[] = []; @@ -165,7 +152,6 @@ export async function getOptimizerCacheKey(config: OptimizerConfig) { const cacheKeys: OptimizerCacheKey = { lastCommit, - bootstrap, deletedPaths, modifiedTimes: {} as Record, workerConfig: config.getCacheableWorkerConfig(), diff --git a/packages/kbn-optimizer/src/optimizer/get_changes.test.ts b/packages/kbn-optimizer/src/optimizer/get_changes.test.ts index d8be1917c101b..d3cc5cceefddf 100644 --- a/packages/kbn-optimizer/src/optimizer/get_changes.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_changes.test.ts @@ -7,22 +7,22 @@ */ jest.mock('execa'); -jest.mock('fs'); import { getChanges } from './get_changes'; +import { REPO_ROOT, createAbsolutePathSerializer } from '@kbn/dev-utils'; const execa: jest.Mock = jest.requireMock('execa'); +expect.addSnapshotSerializer(createAbsolutePathSerializer()); + it('parses git ls-files output', async () => { expect.assertions(4); - jest.requireMock('fs').existsSync.mockImplementation(() => true); - execa.mockImplementation((cmd, args, options) => { expect(cmd).toBe('git'); - expect(args).toEqual(['ls-files', '-dmt', '--', '/foo/bar/x']); + expect(args).toEqual(['ls-files', '-dmt', '--', 'foo/bar/x']); expect(options).toEqual({ - cwd: '/foo/bar/x', + cwd: REPO_ROOT, }); return { @@ -37,12 +37,14 @@ it('parses git ls-files output', async () => { }; }); - await expect(getChanges('/foo/bar/x')).resolves.toMatchInlineSnapshot(` + const changes = await getChanges('foo/bar/x'); + + expect(changes).toMatchInlineSnapshot(` Map { - "/foo/bar/x/kbn-optimizer/package.json" => "modified", - "/foo/bar/x/kbn-optimizer/src/common/bundle.ts" => "modified", - "/foo/bar/x/kbn-optimizer/src/common/bundles.ts" => "deleted", - "/foo/bar/x/kbn-optimizer/src/get_bundle_definitions.test.ts" => "deleted", + /kbn-optimizer/package.json => "modified", + /kbn-optimizer/src/common/bundle.ts => "modified", + /kbn-optimizer/src/common/bundles.ts => "deleted", + /kbn-optimizer/src/get_bundle_definitions.test.ts => "deleted", } `); }); diff --git a/packages/kbn-optimizer/src/optimizer/get_changes.ts b/packages/kbn-optimizer/src/optimizer/get_changes.ts index 73fa5e9d931e7..c5f8abe99c322 100644 --- a/packages/kbn-optimizer/src/optimizer/get_changes.ts +++ b/packages/kbn-optimizer/src/optimizer/get_changes.ts @@ -9,22 +9,19 @@ import Path from 'path'; import execa from 'execa'; -import fs from 'fs'; + +import { REPO_ROOT } from '@kbn/dev-utils'; export type Changes = Map; /** * get the changes in all the context directories (plugin public paths) */ -export async function getChanges(dir: string) { +export async function getChanges(relativeDir: string) { const changes: Changes = new Map(); - if (!fs.existsSync(Path.join(dir, '.git'))) { - return changes; - } - - const { stdout } = await execa('git', ['ls-files', '-dmt', '--', dir], { - cwd: dir, + const { stdout } = await execa('git', ['ls-files', '-dmt', '--', relativeDir], { + cwd: REPO_ROOT, }); const output = stdout.trim(); @@ -32,7 +29,7 @@ export async function getChanges(dir: string) { if (output) { for (const line of output.split('\n')) { const [tag, ...pathParts] = line.trim().split(' '); - const path = Path.resolve(dir, pathParts.join(' ')); + const path = Path.resolve(REPO_ROOT, pathParts.join(' ')); switch (tag) { case 'M': case 'C': From ca42cf92699d3af2d2d22def93cc9c6927d00e18 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 28 Jun 2021 12:34:43 -0500 Subject: [PATCH 02/74] Add @storybook/testing-react (#103004) --- .eslintrc.js | 12 +++ package.json | 1 + .../DetailView/ExceptionStacktrace.test.tsx | 42 ---------- ...s.tsx => exception_stacktrace.stories.tsx} | 78 ++++++++++--------- .../DetailView/exception_stacktrace.test.tsx | 31 ++++++++ ...tacktrace.tsx => exception_stacktrace.tsx} | 0 .../ErrorGroupDetails/DetailView/index.tsx | 2 +- .../link_preview.test.tsx | 45 +++++------ yarn.lock | 5 ++ 9 files changed, 109 insertions(+), 107 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx rename x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/{ExceptionStacktrace.stories.tsx => exception_stacktrace.stories.tsx} (98%) create mode 100644 x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.test.tsx rename x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/{ExceptionStacktrace.tsx => exception_stacktrace.tsx} (100%) diff --git a/.eslintrc.js b/.eslintrc.js index c64f03a8398e5..2eea41984b30e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -857,6 +857,18 @@ module.exports = { 'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useFetcher$' }], }, }, + { + files: ['x-pack/plugins/apm/**/*.stories.*', 'x-pack/plugins/observability/**/*.stories.*'], + rules: { + 'react/function-component-definition': [ + 'off', + { + namedComponents: 'function-declaration', + unnamedComponents: 'arrow-function', + }, + ], + }, + }, /** * Fleet overrides diff --git a/package.json b/package.json index b071b587a3620..2e22a4e0ccf77 100644 --- a/package.json +++ b/package.json @@ -493,6 +493,7 @@ "@storybook/core-events": "^6.1.20", "@storybook/node-logger": "^6.1.20", "@storybook/react": "^6.1.20", + "@storybook/testing-react": "^0.0.17", "@storybook/theming": "^6.1.20", "@testing-library/dom": "^7.30.3", "@testing-library/jest-dom": "^5.11.10", diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx deleted file mode 100644 index f3f46f50f6020..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { ExceptionStacktrace } from './ExceptionStacktrace'; - -describe('ExceptionStacktrace', () => { - describe('render', () => { - it('renders', () => { - const props = { exceptions: [] }; - - expect(() => - shallow() - ).not.toThrowError(); - }); - - describe('with a stack trace', () => { - it('renders the stack trace', () => { - const props = { exceptions: [{}] }; - - expect( - shallow().find('Stacktrace') - ).toHaveLength(1); - }); - }); - - describe('with more than one stack trace', () => { - it('renders a cause stack trace', () => { - const props = { exceptions: [{}, {}] }; - - expect( - shallow().find('CauseStacktrace') - ).toHaveLength(1); - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.stories.tsx similarity index 98% rename from x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx rename to x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.stories.tsx index 8616bf29cb97f..f21c189584d31 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.stories.tsx @@ -5,27 +5,31 @@ * 2.0. */ -import React, { ComponentType } from 'react'; +import { Story } from '@storybook/react'; +import React, { ComponentProps, ComponentType } from 'react'; import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; -import { Exception } from '../../../../../typings/es_schemas/raw/error_raw'; -import { ExceptionStacktrace } from './ExceptionStacktrace'; +import { ExceptionStacktrace } from './exception_stacktrace'; + +type Args = ComponentProps; export default { title: 'app/ErrorGroupDetails/DetailView/ExceptionStacktrace', component: ExceptionStacktrace, decorators: [ - (Story: ComponentType) => { - return ( - - - - ); - }, + (StoryComponent: ComponentType) => ( + + + + ), ], }; -export function JavaWithLongLines() { - const exceptions: Exception[] = [ +export const JavaWithLongLines: Story = (args) => ( + +); +JavaWithLongLines.args = { + codeLanguage: 'java', + exceptions: [ { stacktrace: [ { @@ -1734,22 +1738,23 @@ export function JavaWithLongLines() { 'Null return value from advice does not match primitive return type for: public abstract double co.elastic.apm.opbeans.repositories.Numbers.getRevenue()', type: 'org.springframework.aop.AopInvocationException', }, - ]; - - return ; -} + ], +}; JavaWithLongLines.decorators = [ - (Story: ComponentType) => { - return ( -
- -
- ); - }, + (StoryComponent: ComponentType) => ( +
+ +
+ ), ]; -export function JavaScriptWithSomeContext() { - const exceptions: Exception[] = [ +export const JavaScriptWithSomeContext: Story = (args) => ( + +); +JavaScriptWithSomeContext.storyName = 'JavaScript With Some Context'; +JavaScriptWithSomeContext.args = { + codeLanguage: 'javascript', + exceptions: [ { code: '503', stacktrace: [ @@ -1870,16 +1875,15 @@ export function JavaScriptWithSomeContext() { type: 'Error', message: 'Unexpected APM Server response when polling config', }, - ]; - - return ( - - ); -} -JavaScriptWithSomeContext.storyName = 'JavaScript With Some Context'; + ], +}; -export function RubyWithContextAndLibraryFrames() { - const exceptions: Exception[] = [ +export const RubyWithContextAndLibraryFrames: Story = (args) => ( + +); +RubyWithContextAndLibraryFrames.args = { + codeLanguage: 'ruby', + exceptions: [ { stacktrace: [ { @@ -2536,7 +2540,5 @@ export function RubyWithContextAndLibraryFrames() { message: "Couldn't find Order with 'id'=956", type: 'ActiveRecord::RecordNotFound', }, - ]; - - return ; -} + ], +}; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.test.tsx new file mode 100644 index 0000000000000..9417c0c584f1b --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { composeStories } from '@storybook/testing-react'; +import React from 'react'; +import { mount } from 'enzyme'; +import * as stories from './exception_stacktrace.stories'; + +const { JavaWithLongLines } = composeStories(stories); + +describe('ExceptionStacktrace', () => { + describe('render', () => { + describe('with stacktraces', () => { + it('renders the stacktraces', () => { + expect(mount().find('Stacktrace')).toHaveLength(3); + }); + }); + + describe('with more than one stack trace', () => { + it('renders cause stacktraces', () => { + expect( + mount().find('CauseStacktrace') + ).toHaveLength(2); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx rename to x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/exception_stacktrace.tsx diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 11926dd965f95..ae67ec868d766 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -39,7 +39,7 @@ import { getTabs, logStacktraceTab, } from './ErrorTabs'; -import { ExceptionStacktrace } from './ExceptionStacktrace'; +import { ExceptionStacktrace } from './exception_stacktrace'; const HeaderContainer = euiStyled.div` display: flex; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx index 407f460f25ad3..d88cef0702a87 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/link_preview.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import React from 'react'; -import { LinkPreview } from '../CreateEditCustomLinkFlyout/link_preview'; +import { composeStories } from '@storybook/testing-react'; import { render, getNodeText, @@ -14,24 +13,15 @@ import { act, waitFor, } from '@testing-library/react'; -import { - getCallApmApiSpy, - CallApmApiSpy, -} from '../../../../../../services/rest/callApmApiSpy'; +import React from 'react'; +import * as stories from './link_preview.stories'; + +const { Example } = composeStories(stories); export const removeExternalLinkText = (str: string) => str.replace(/\(opens in a new tab or window\)/g, ''); describe('LinkPreview', () => { - let callApmApiSpy: CallApmApiSpy; - beforeAll(() => { - callApmApiSpy = getCallApmApiSpy().mockResolvedValue({ - transaction: { id: 'foo' }, - }); - }); - afterAll(() => { - jest.clearAllMocks(); - }); const getElementValue = (container: HTMLElement, id: string) => getNodeText( ((getByTestId(container, id) as HTMLDivElement) @@ -41,7 +31,7 @@ describe('LinkPreview', () => { it('shows label and url default values', () => { act(() => { const { container } = render( - + ); expect(getElementValue(container, 'preview-label')).toEqual('Elastic.co'); expect(getElementValue(container, 'preview-url')).toEqual( @@ -53,7 +43,7 @@ describe('LinkPreview', () => { it('shows label and url values', () => { act(() => { const { container } = render( - { it("shows warning when couldn't replace context variables", () => { act(() => { const { container } = render( - { expect(getByTestId(container, 'preview-warning')).toBeInTheDocument(); }); }); + it('replaces url with transaction id', async () => { const { container } = render( - ); - await waitFor(() => expect(callApmApiSpy).toHaveBeenCalled()); - expect(getElementValue(container, 'preview-label')).toEqual('foo'); - expect( - removeExternalLinkText( - (getByTestId(container, 'preview-link') as HTMLAnchorElement).text - ) - ).toEqual('https://baz.co?transaction=foo'); + + await waitFor(() => { + expect(getElementValue(container, 'preview-label')).toEqual('foo'); + expect( + removeExternalLinkText( + (getByTestId(container, 'preview-link') as HTMLAnchorElement).text + ) + ).toEqual('https://baz.co?transaction=0'); + }); }); }); diff --git a/yarn.lock b/yarn.lock index e07b3edff1445..ea968cf3631f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4307,6 +4307,11 @@ regenerator-runtime "^0.13.7" source-map "^0.7.3" +"@storybook/testing-react@^0.0.17": + version "0.0.17" + resolved "https://registry.yarnpkg.com/@storybook/testing-react/-/testing-react-0.0.17.tgz#632dd22f8815743f78c182b126f444cf51d92d71" + integrity sha512-93nbA/JSWDEys1msd438+wzETRFDEgT2aFqJL2y46++zsyv8g2mCYKZkf9E36KQHMQbO1uJBHT8CmrLQa8VmZw== + "@storybook/theming@6.1.20", "@storybook/theming@^6.1.20": version "6.1.20" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.1.20.tgz#ed0b330a5c08bbe998e9df95e615f0e84a8d663f" From 60d1ef9f92226135a7c648259e380513793efd57 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 28 Jun 2021 10:55:53 -0700 Subject: [PATCH 03/74] [Enterprise Search] Add beta notification (#103429) * Set up new BetaNotification component * Update shared page template to append new beta notification item to side nav NOTE: I'm mutating the array because: - returning a new instance leads to a lot of really annoying type errors - the side nav's we're getting are entirely static with predictable items & and always come from us anyway - this is eventually going to get removed, and I'm optimizing for easy-to-remove code * Add beta notification to error connecting state - to help users/SDH cases where users cannot connect at all --- .../shared/error_state/error_state_prompt.tsx | 10 +- .../applications/shared/layout/beta.scss | 27 ++++ .../applications/shared/layout/beta.test.tsx | 131 ++++++++++++++++++ .../applications/shared/layout/beta.tsx | 109 +++++++++++++++ .../shared/layout/page_template.test.tsx | 2 + .../shared/layout/page_template.tsx | 4 + 6 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index f855c7b67dc6e..5636b56cc33af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -12,7 +12,8 @@ import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KibanaLogic } from '../../shared/kibana'; +import { KibanaLogic } from '../kibana'; +import { BetaNotification } from '../layout/beta'; import { EuiButtonTo } from '../react_router_helpers'; import './error_state_prompt.scss'; @@ -92,14 +93,15 @@ export const ErrorStatePrompt: React.FC = () => { } - actions={ + actions={[ - - } + , + , + ]} /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss new file mode 100644 index 0000000000000..6ba90cba381c4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +@include euiBreakpoint('m', 'l', 'xl') { + .kbnPageTemplateSolutionNav { + position: relative; + min-height: 100%; + + // Nested to override EUI specificity + .betaNotificationSideNavItem { + margin-top: $euiSizeL; + } + } + + .betaNotificationWrapper { + position: absolute; + bottom: 3px; // Without this 3px buffer, the popover won't render to the right + } +} + +.betaNotification { + width: 350px; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx new file mode 100644 index 0000000000000..91c3cf8881b8a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../__mocks__/enterprise_search_url.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiPopover, EuiPopoverTitle, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { shallowWithIntl } from '../../test_helpers'; + +import { BetaNotification, appendBetaNotificationItem } from './beta'; + +describe('BetaNotification', () => { + const getToggleButton = (wrapper: ShallowWrapper) => { + return shallow(
{wrapper.prop('button')}
).childAt(0); + }; + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.type()).toEqual(EuiPopover); + expect(wrapper.find(EuiPopoverTitle).prop('children')).toEqual( + 'Enterprise Search in Kibana is a beta user interface' + ); + }); + + describe('open/close popover state', () => { + const wrapper = shallow(); + + it('is initially closed', () => { + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); + }); + + it('opens the popover when the toggle button is pressed', () => { + getToggleButton(wrapper).simulate('click'); + + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(true); + }); + + it('closes the popover', () => { + wrapper.prop('closePopover')(); + + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); + }); + }); + + describe('toggle button props', () => { + it('defaults to a size of xs and flush', () => { + const wrapper = shallow(); + const toggleButton = getToggleButton(wrapper); + + expect(toggleButton.prop('size')).toEqual('xs'); + expect(toggleButton.prop('flush')).toEqual('both'); + }); + + it('passes down custom button props', () => { + const wrapper = shallow(); + const toggleButton = getToggleButton(wrapper); + + expect(toggleButton.prop('size')).toEqual('l'); + }); + }); + + describe('links', () => { + const wrapper = shallowWithIntl(); + const links = wrapper.find(FormattedMessage).dive(); + + it('renders a documentation link', () => { + const docLink = links.find(EuiLink).first(); + + expect(docLink.prop('href')).toContain('/user-interfaces.html'); + }); + + it('renders a link back to the standalone UI', () => { + const switchLink = links.find(EuiLink).last(); + + expect(switchLink.prop('href')).toBe('http://localhost:3002'); + }); + }); +}); + +describe('appendBetaNotificationItem', () => { + const mockSideNav = { + name: 'Hello world', + items: [ + { id: '1', name: 'Link 1' }, + { id: '2', name: 'Link 2' }, + ], + }; + + it('inserts a beta notification into a side nav items array', () => { + appendBetaNotificationItem(mockSideNav); + + expect(mockSideNav).toEqual({ + name: 'Hello world', + items: [ + { id: '1', name: 'Link 1' }, + { id: '2', name: 'Link 2' }, + { + id: 'beta', + name: '', + className: 'betaNotificationSideNavItem', + renderItem: expect.any(Function), + }, + ], + }); + }); + + it('renders the BetaNotification component as a side nav item', () => { + const SideNavItem = (mockSideNav.items[2] as any).renderItem; + const wrapper = shallow(); + + expect(wrapper.hasClass('betaNotificationWrapper')).toBe(true); + expect(wrapper.find(BetaNotification)).toHaveLength(1); + }); + + it('does nothing if a sidenav with no items was passed', () => { + const mockEmptySideNav = { name: 'empty' }; + appendBetaNotificationItem(mockEmptySideNav); + + expect(mockEmptySideNav).toEqual({ name: 'empty' }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx new file mode 100644 index 0000000000000..14f2d54b55398 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { + EuiPopover, + EuiPopoverTitle, + EuiPopoverFooter, + EuiButtonEmpty, + EuiButtonEmptyProps, + EuiText, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public'; + +import { docLinks } from '../doc_links'; +import { getEnterpriseSearchUrl } from '../enterprise_search_url'; + +import './beta.scss'; + +interface Props { + buttonProps?: EuiButtonEmptyProps; +} + +export const BetaNotification: React.FC = ({ buttonProps }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = () => setIsPopoverOpen((isOpen) => !isOpen); + const closePopover = () => setIsPopoverOpen(false); + + return ( + + {i18n.translate('xpack.enterpriseSearch.beta.buttonLabel', { + defaultMessage: 'This is a beta user interface', + })} + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="rightDown" + repositionOnScroll + > + + {i18n.translate('xpack.enterpriseSearch.beta.popover.title', { + defaultMessage: 'Enterprise Search in Kibana is a beta user interface', + })} + +
+ +

+ {i18n.translate('xpack.enterpriseSearch.beta.popover.description', { + defaultMessage: + 'The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.', + })} +

+
+
+ + + Learn more + + ), + standaloneUILink: ( + switch to the Enterprise Search UI + ), + }} + /> + +
+ ); +}; + +export const appendBetaNotificationItem = (sideNav: KibanaPageTemplateProps['solutionNav']) => { + if (sideNav?.items) { + sideNav.items.push({ + id: 'beta', + name: '', + className: 'betaNotificationSideNavItem', + renderItem: () => ( +
+ +
+ ), + }); + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index 5b02756e44b52..bc612de884f8b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -7,6 +7,8 @@ import { setMockValues } from '../../__mocks__/kea_logic'; +jest.mock('./beta', () => ({ appendBetaNotificationItem: jest.fn() })); // Mostly adding this to get tests passing. Should be removed once we're out of beta + import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index affec11921545..5da455283eceb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -23,6 +23,8 @@ import { HttpLogic } from '../http'; import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; import { Loading } from '../loading'; +import { appendBetaNotificationItem } from './beta'; + import './page_template.scss'; /* @@ -61,6 +63,8 @@ export const EnterpriseSearchPageTemplate: React.FC = ({ const hasCustomEmptyState = !!emptyState; const showCustomEmptyState = hasCustomEmptyState && isEmptyState; + appendBetaNotificationItem(solutionNav); + return ( Date: Mon, 28 Jun 2021 19:58:10 +0200 Subject: [PATCH 04/74] Fix 404 error on deleted rule alert view (#103491) --- .../event_details/alert_summary_view.test.tsx | 10 +++++----- .../components/event_details/alert_summary_view.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index c782804b0592b..ed0a957fd2dd3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -11,7 +11,7 @@ import { waitFor } from '@testing-library/react'; import { AlertSummaryView } from './alert_summary_view'; import { mockAlertDetailsData } from './__mocks__'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; +import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; import { TestProviders } from '../../mock'; import { mockBrowserFields } from '../../containers/source/mock'; @@ -19,9 +19,9 @@ import { useMountAppended } from '../../utils/use_mount_appended'; jest.mock('../../lib/kibana'); -jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { +jest.mock('../../../detections/containers/detection_engine/rules/use_rule_with_fallback', () => { return { - useRuleAsync: jest.fn(), + useRuleWithFallback: jest.fn(), }; }); @@ -37,7 +37,7 @@ describe('AlertSummaryView', () => { beforeEach(() => { jest.clearAllMocks(); - (useRuleAsync as jest.Mock).mockReturnValue({ + (useRuleWithFallback as jest.Mock).mockReturnValue({ rule: { note: 'investigation guide', }, @@ -64,7 +64,7 @@ describe('AlertSummaryView', () => { }); test("render no investigation guide if it doesn't exist", async () => { - (useRuleAsync as jest.Mock).mockReturnValue({ + (useRuleWithFallback as jest.Mock).mockReturnValue({ rule: { note: null, }, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 9cc0b43f52123..59acb16c028d8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -35,7 +35,7 @@ import { import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../network/components/ip'; import { SummaryView } from './summary_view'; import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; -import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; +import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; import { LineClamp } from '../line_clamp'; import { endpointAlertCheck } from '../../utils/endpoint_alert_check'; @@ -207,7 +207,7 @@ const AlertSummaryViewComponent: React.FC<{ ? item?.originalValue[0] : item?.originalValue ?? null; }, [data]); - const { rule: maybeRule } = useRuleAsync(ruleId); + const { rule: maybeRule } = useRuleWithFallback(ruleId); return ( <> From 0f9b715dff44719287f4772d9b0058a13a8e674b Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 28 Jun 2021 20:02:44 +0200 Subject: [PATCH 05/74] [APM] Invert tint fraction after polished upgrade (#103439) Closes #103061. We use polished.tint() in several places to have a subdued highlight for an element. With the polished upgrade to 3.x from several weeks ago came a bug fix for tint() that applied the tint fraction in a different way. The fix for us to invert those fractions (eg 0.1 becomes 0.9). --- .../Waterfall/SpanFlyout/DatabaseContext.tsx | 2 +- .../public/components/shared/KueryBar/Typeahead/Suggestion.js | 2 +- .../components/shared/KueryBar/Typeahead/Suggestions.js | 2 +- .../apm/public/components/shared/Stacktrace/Context.tsx | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx index fda2d595e669d..6fd6873cf565e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/DatabaseContext.tsx @@ -28,7 +28,7 @@ SyntaxHighlighter.registerLanguage('sql', sql); const DatabaseStatement = euiStyled.div` padding: ${px(units.half)} ${px(unit)}; - background: ${({ theme }) => tint(0.1, theme.eui.euiColorWarning)}; + background: ${({ theme }) => tint(0.9, theme.eui.euiColorWarning)}; border-radius: ${borderRadius}; border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; font-family: ${fontFamilyCode}; diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js index 46da6fe4be4c9..987daf67b1fc7 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestion.js @@ -70,7 +70,7 @@ const ListItem = euiStyled.li` const Icon = euiStyled.div` flex: 0 0 ${px(units.double)}; - background: ${({ type, theme }) => tint(0.1, getIconColor(type, theme))}; + background: ${({ type, theme }) => tint(0.9, getIconColor(type, theme))}; color: ${({ type, theme }) => getIconColor(type, theme)}; width: 100%; height: 100%; diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js index e5f0d866e254c..405be89c6629c 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/Typeahead/Suggestions.js @@ -18,7 +18,7 @@ const List = euiStyled.ul` border: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; border-radius: ${px(units.quarter)}; box-shadow: 0px ${px(units.quarter)} ${px(units.double)} - ${({ theme }) => tint(0.1, theme.eui.euiColorFullShade)}; + ${({ theme }) => tint(0.9, theme.eui.euiColorFullShade)}; position: absolute; background: ${({ theme }) => theme.eui.euiColorEmptyShade}; z-index: 10; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx index 85d29dda95b5c..ef74894169d72 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -33,7 +33,7 @@ const LineHighlight = euiStyled.div<{ lineNumber: number }>` height: ${px(units.eighth * 9)}; top: ${(props) => px(props.lineNumber * LINE_HEIGHT)}; pointer-events: none; - background-color: ${({ theme }) => tint(0.1, theme.eui.euiColorWarning)}; + background-color: ${({ theme }) => tint(0.9, theme.eui.euiColorWarning)}; `; const LineNumberContainer = euiStyled.div<{ isLibraryFrame: boolean }>` @@ -57,7 +57,7 @@ const LineNumber = euiStyled.div<{ highlight: boolean }>` text-align: right; border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; background-color: ${({ highlight, theme }) => - highlight ? tint(0.1, theme.eui.euiColorWarning) : null}; + highlight ? tint(0.9, theme.eui.euiColorWarning) : null}; &:last-of-type { border-radius: 0 0 0 ${borderRadius}; From b51af01adc348a847c695c5bbe57996c15af7ae6 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 28 Jun 2021 20:03:30 +0200 Subject: [PATCH 06/74] [APM] Support records in strict_keys_rt (#103391) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-io-ts-utils/BUILD.bazel | 1 + packages/kbn-io-ts-utils/src/index.ts | 1 + .../src/props_to_schema/index.ts | 27 +++ .../src/strict_keys_rt/index.test.ts | 145 +++++++++++- .../src/strict_keys_rt/index.ts | 223 +++++++----------- .../src/to_json_schema/index.test.ts | 64 +++++ .../src/to_json_schema/index.ts | 115 +++++++++ .../routes/register_routes/index.test.ts | 2 +- 8 files changed, 440 insertions(+), 138 deletions(-) create mode 100644 packages/kbn-io-ts-utils/src/props_to_schema/index.ts create mode 100644 packages/kbn-io-ts-utils/src/to_json_schema/index.test.ts create mode 100644 packages/kbn-io-ts-utils/src/to_json_schema/index.ts diff --git a/packages/kbn-io-ts-utils/BUILD.bazel b/packages/kbn-io-ts-utils/BUILD.bazel index 6b26173fe8f36..053030a6f11a9 100644 --- a/packages/kbn-io-ts-utils/BUILD.bazel +++ b/packages/kbn-io-ts-utils/BUILD.bazel @@ -25,6 +25,7 @@ NPM_MODULE_EXTRA_FILES = [ ] SRC_DEPS = [ + "//packages/kbn-config-schema", "@npm//fp-ts", "@npm//io-ts", "@npm//lodash", diff --git a/packages/kbn-io-ts-utils/src/index.ts b/packages/kbn-io-ts-utils/src/index.ts index 418a5a41a2bec..a60bc2086fa3a 100644 --- a/packages/kbn-io-ts-utils/src/index.ts +++ b/packages/kbn-io-ts-utils/src/index.ts @@ -12,3 +12,4 @@ export { strictKeysRt } from './strict_keys_rt'; export { isoToEpochRt } from './iso_to_epoch_rt'; export { toNumberRt } from './to_number_rt'; export { toBooleanRt } from './to_boolean_rt'; +export { toJsonSchema } from './to_json_schema'; diff --git a/packages/kbn-io-ts-utils/src/props_to_schema/index.ts b/packages/kbn-io-ts-utils/src/props_to_schema/index.ts new file mode 100644 index 0000000000000..5915df1b0102e --- /dev/null +++ b/packages/kbn-io-ts-utils/src/props_to_schema/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as t from 'io-ts'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { schema, Type } from '@kbn/config-schema'; +import { isLeft } from 'fp-ts/lib/Either'; + +export function propsToSchema>(type: T): Type> { + return schema.object( + {}, + { + unknowns: 'allow', + validate: (val) => { + const decoded = type.decode(val); + + if (isLeft(decoded)) { + return PathReporter.report(decoded).join('\n'); + } + }, + } + ); +} diff --git a/packages/kbn-io-ts-utils/src/strict_keys_rt/index.test.ts b/packages/kbn-io-ts-utils/src/strict_keys_rt/index.test.ts index ab20ca42a283e..6b19026cb1be5 100644 --- a/packages/kbn-io-ts-utils/src/strict_keys_rt/index.test.ts +++ b/packages/kbn-io-ts-utils/src/strict_keys_rt/index.test.ts @@ -10,9 +10,76 @@ import * as t from 'io-ts'; import { isRight, isLeft } from 'fp-ts/lib/Either'; import { strictKeysRt } from './'; import { jsonRt } from '../json_rt'; +import { PathReporter } from 'io-ts/lib/PathReporter'; describe('strictKeysRt', () => { it('correctly and deeply validates object keys', () => { + const metricQueryRt = t.union( + [ + t.type({ + avg_over_time: t.intersection([ + t.type({ + field: t.string, + }), + t.partial({ + range: t.string, + }), + ]), + }), + t.type({ + count_over_time: t.strict({}), + }), + ], + 'metric_query' + ); + + const metricExpressionRt = t.type( + { + expression: t.string, + }, + 'metric_expression' + ); + + const metricRt = t.intersection([ + t.partial({ + record: t.boolean, + }), + t.union([metricQueryRt, metricExpressionRt]), + ]); + + const metricContainerRt = t.record(t.string, metricRt); + + const groupingRt = t.type( + { + by: t.record( + t.string, + t.type({ + field: t.string, + }), + 'by' + ), + limit: t.number, + }, + 'grouping' + ); + + const queryRt = t.intersection( + [ + t.union([groupingRt, t.strict({})]), + t.type({ + index: t.union([t.string, t.array(t.string)]), + metrics: metricContainerRt, + }), + t.partial({ + filter: t.string, + round: t.string, + runtime_mappings: t.string, + query_delay: t.string, + }), + ], + 'query' + ); + const checks: Array<{ type: t.Type; passes: any[]; fails: any[] }> = [ { type: t.intersection([t.type({ foo: t.string }), t.partial({ bar: t.string })]), @@ -42,6 +109,78 @@ describe('strictKeysRt', () => { passes: [{ query: { bar: '', _inspect: true } }], fails: [{ query: { _inspect: true } }], }, + { + type: t.type({ + body: t.intersection([ + t.partial({ + from: t.string, + }), + t.type({ + config: t.intersection([ + t.partial({ + from: t.string, + }), + t.type({ + alert: t.type({}), + }), + t.union([ + t.type({ + query: queryRt, + }), + t.type({ + queries: t.array(queryRt), + }), + ]), + ]), + }), + ]), + }), + passes: [ + { + body: { + config: { + alert: {}, + query: { + index: ['apm-*'], + filter: 'processor.event:transaction', + metrics: { + avg_latency_1h: { + avg_over_time: { + field: 'transaction.duration.us', + }, + }, + }, + }, + }, + }, + }, + ], + fails: [ + { + body: { + config: { + alert: {}, + query: { + index: '', + metrics: { + avg_latency_1h: { + avg_over_time: { + field: '', + range: '', + }, + }, + rate_1h: { + count_over_time: { + field: '', + }, + }, + }, + }, + }, + }, + }, + ], + }, ]; checks.forEach((check) => { @@ -54,9 +193,9 @@ describe('strictKeysRt', () => { if (!isRight(result)) { throw new Error( - `Expected ${JSON.stringify(value)} to be allowed, but validation failed with ${ - result.left[0].message - }` + `Expected ${JSON.stringify( + value + )} to be allowed, but validation failed with ${PathReporter.report(result).join('\n')}` ); } }); diff --git a/packages/kbn-io-ts-utils/src/strict_keys_rt/index.ts b/packages/kbn-io-ts-utils/src/strict_keys_rt/index.ts index 56afdf54463f7..cb3d9bb2100d0 100644 --- a/packages/kbn-io-ts-utils/src/strict_keys_rt/index.ts +++ b/packages/kbn-io-ts-utils/src/strict_keys_rt/index.ts @@ -7,9 +7,9 @@ */ import * as t from 'io-ts'; -import { either, isRight } from 'fp-ts/lib/Either'; -import { mapValues, difference, isPlainObject, forEach } from 'lodash'; -import { MergeType, mergeRt } from '../merge_rt'; +import { either } from 'fp-ts/lib/Either'; +import { difference, isPlainObject, forEach } from 'lodash'; +import { MergeType } from '../merge_rt'; /* Type that tracks validated keys, and fails when the input value @@ -17,153 +17,108 @@ import { MergeType, mergeRt } from '../merge_rt'; */ type ParsableType = - | t.IntersectionType - | t.UnionType + | t.IntersectionType + | t.UnionType | t.PartialType - | t.ExactType + | t.ExactType | t.InterfaceType - | MergeType; - -function getKeysInObject>( - object: T, - prefix: string = '' -): string[] { - const keys: string[] = []; - forEach(object, (value, key) => { - const ownPrefix = prefix ? `${prefix}.${key}` : key; - keys.push(ownPrefix); - if (isPlainObject(object[key])) { - keys.push(...getKeysInObject(object[key] as Record, ownPrefix)); - } - }); - return keys; + | MergeType + | t.DictionaryType; + +const tags = [ + 'DictionaryType', + 'IntersectionType', + 'MergeType', + 'InterfaceType', + 'PartialType', + 'ExactType', + 'UnionType', +]; + +function isParsableType(type: t.Mixed): type is ParsableType { + return tags.includes((type as any)._tag); } -function addToContextWhenValidated | t.PartialType>( - type: T, - prefix: string -): T { - const validate = (input: unknown, context: t.Context) => { - const result = type.validate(input, context); - const keysType = context[0].type as StrictKeysType; - if (!('trackedKeys' in keysType)) { - throw new Error('Expected a top-level StrictKeysType'); - } - if (isRight(result)) { - keysType.trackedKeys.push(...Object.keys(type.props).map((propKey) => `${prefix}${propKey}`)); - } - return result; - }; - - if (type._tag === 'InterfaceType') { - return new t.InterfaceType(type.name, type.is, validate, type.encode, type.props) as T; +function getHandlingTypes(type: t.Mixed, key: string, value: object): t.Mixed[] { + if (!isParsableType(type)) { + return []; } - return new t.PartialType(type.name, type.is, validate, type.encode, type.props) as T; -} + switch (type._tag) { + case 'DictionaryType': + return [type.codomain]; -function trackKeysOfValidatedTypes(type: ParsableType | t.Any, prefix: string = ''): t.Any { - if (!('_tag' in type)) { - return type; - } - const taggedType = type as ParsableType; - - switch (taggedType._tag) { - case 'IntersectionType': { - const collectionType = type as t.IntersectionType; - return t.intersection( - collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [t.Any, t.Any] - ); - } + case 'IntersectionType': + return type.types.map((i) => getHandlingTypes(i, key, value)).flat(); - case 'UnionType': { - const collectionType = type as t.UnionType; - return t.union( - collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [t.Any, t.Any] - ); - } + case 'MergeType': + return type.types.map((i) => getHandlingTypes(i, key, value)).flat(); - case 'MergeType': { - const collectionType = type as MergeType; - return mergeRt( - ...(collectionType.types.map((rt) => trackKeysOfValidatedTypes(rt, prefix)) as [ - t.Any, - t.Any - ]) - ); - } + case 'InterfaceType': + case 'PartialType': + return [type.props[key]]; - case 'PartialType': { - const propsType = type as t.PartialType; - - return addToContextWhenValidated( - t.partial( - mapValues(propsType.props, (val, key) => - trackKeysOfValidatedTypes(val, `${prefix}${key}.`) - ) - ), - prefix - ); - } + case 'ExactType': + return getHandlingTypes(type.type, key, value); - case 'InterfaceType': { - const propsType = type as t.InterfaceType; - - return addToContextWhenValidated( - t.type( - mapValues(propsType.props, (val, key) => - trackKeysOfValidatedTypes(val, `${prefix}${key}.`) - ) - ), - prefix - ); - } + case 'UnionType': + const matched = type.types.find((m) => m.is(value)); + return matched ? getHandlingTypes(matched, key, value) : []; + } +} - case 'ExactType': { - const exactType = type as t.ExactType; +function getHandledKeys>( + type: t.Mixed, + object: T, + prefix: string = '' +): { handled: Set; all: Set } { + const keys: { + handled: Set; + all: Set; + } = { + handled: new Set(), + all: new Set(), + }; - return t.exact(trackKeysOfValidatedTypes(exactType.type, prefix) as t.HasProps); + forEach(object, (value, key) => { + const ownPrefix = prefix ? `${prefix}.${key}` : key; + keys.all.add(ownPrefix); + + const handlingTypes = getHandlingTypes(type, key, object).filter(Boolean); + + if (handlingTypes.length) { + keys.handled.add(ownPrefix); } - default: - return type; - } -} + if (isPlainObject(value)) { + handlingTypes.forEach((i) => { + const nextKeys = getHandledKeys(i, value as Record, ownPrefix); + nextKeys.all.forEach((k) => keys.all.add(k)); + nextKeys.handled.forEach((k) => keys.handled.add(k)); + }); + } + }); -class StrictKeysType< - A = any, - O = A, - I = any, - T extends t.Type = t.Type -> extends t.Type { - trackedKeys: string[]; - - constructor(type: T) { - const trackedType = trackKeysOfValidatedTypes(type); - - super( - 'strict_keys', - trackedType.is, - (input, context) => { - this.trackedKeys.length = 0; - return either.chain(trackedType.validate(input, context), (i) => { - const originalKeys = getKeysInObject(input as Record); - const excessKeys = difference(originalKeys, this.trackedKeys); - - if (excessKeys.length) { - return t.failure(i, context, `Excess keys are not allowed: \n${excessKeys.join('\n')}`); - } - - return t.success(i); - }); - }, - trackedType.encode - ); - - this.trackedKeys = []; - } + return keys; } -export function strictKeysRt(type: T): T { - return (new StrictKeysType(type) as unknown) as T; +export function strictKeysRt(type: T) { + return new t.Type( + type.name, + type.is, + (input, context) => { + return either.chain(type.validate(input, context), (i) => { + const keys = getHandledKeys(type, input as Record); + + const excessKeys = difference([...keys.all], [...keys.handled]); + + if (excessKeys.length) { + return t.failure(i, context, `Excess keys are not allowed: \n${excessKeys.join('\n')}`); + } + + return t.success(i); + }); + }, + type.encode + ); } diff --git a/packages/kbn-io-ts-utils/src/to_json_schema/index.test.ts b/packages/kbn-io-ts-utils/src/to_json_schema/index.test.ts new file mode 100644 index 0000000000000..cac7d3b2aae5e --- /dev/null +++ b/packages/kbn-io-ts-utils/src/to_json_schema/index.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as t from 'io-ts'; +import { toJsonSchema } from './'; + +describe('toJsonSchema', () => { + it('converts simple types to JSON schema', () => { + expect( + toJsonSchema( + t.type({ + foo: t.string, + }) + ) + ).toEqual({ + type: 'object', + properties: { + foo: { + type: 'string', + }, + }, + required: ['foo'], + }); + + expect( + toJsonSchema( + t.type({ + foo: t.union([t.boolean, t.string]), + }) + ) + ).toEqual({ + type: 'object', + properties: { + foo: { + anyOf: [{ type: 'boolean' }, { type: 'string' }], + }, + }, + required: ['foo'], + }); + }); + + it('converts record/dictionary types', () => { + expect( + toJsonSchema( + t.record( + t.string, + t.intersection([t.type({ foo: t.string }), t.partial({ bar: t.array(t.boolean) })]) + ) + ) + ).toEqual({ + type: 'object', + additionalProperties: { + allOf: [ + { type: 'object', properties: { foo: { type: 'string' } }, required: ['foo'] }, + { type: 'object', properties: { bar: { type: 'array', items: { type: 'boolean' } } } }, + ], + }, + }); + }); +}); diff --git a/packages/kbn-io-ts-utils/src/to_json_schema/index.ts b/packages/kbn-io-ts-utils/src/to_json_schema/index.ts new file mode 100644 index 0000000000000..fc196a7c3123e --- /dev/null +++ b/packages/kbn-io-ts-utils/src/to_json_schema/index.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as t from 'io-ts'; +import { mapValues } from 'lodash'; + +type JSONSchemableValueType = + | t.StringType + | t.NumberType + | t.BooleanType + | t.ArrayType + | t.RecordC + | t.DictionaryType + | t.InterfaceType + | t.PartialType + | t.UnionType + | t.IntersectionType; + +const tags = [ + 'StringType', + 'NumberType', + 'BooleanType', + 'ArrayType', + 'DictionaryType', + 'InterfaceType', + 'PartialType', + 'UnionType', + 'IntersectionType', +]; + +const isSchemableValueType = (type: t.Mixed): type is JSONSchemableValueType => { + // @ts-ignore + return tags.includes(type._tag); +}; + +interface JSONSchemaObject { + type: 'object'; + required?: string[]; + properties?: Record; + additionalProperties?: boolean | JSONSchema; +} + +interface JSONSchemaOneOf { + oneOf: JSONSchema[]; +} + +interface JSONSchemaAllOf { + allOf: JSONSchema[]; +} + +interface JSONSchemaAnyOf { + anyOf: JSONSchema[]; +} + +interface JSONSchemaArray { + type: 'array'; + items?: JSONSchema; +} + +interface BaseJSONSchema { + type: string; +} + +type JSONSchema = + | JSONSchemaObject + | JSONSchemaArray + | BaseJSONSchema + | JSONSchemaOneOf + | JSONSchemaAllOf + | JSONSchemaAnyOf; + +export const toJsonSchema = (type: t.Mixed): JSONSchema => { + if (isSchemableValueType(type)) { + switch (type._tag) { + case 'ArrayType': + return { type: 'array', items: toJsonSchema(type.type) }; + + case 'BooleanType': + return { type: 'boolean' }; + + case 'DictionaryType': + return { type: 'object', additionalProperties: toJsonSchema(type.codomain) }; + + case 'InterfaceType': + return { + type: 'object', + properties: mapValues(type.props, toJsonSchema), + required: Object.keys(type.props), + }; + + case 'PartialType': + return { type: 'object', properties: mapValues(type.props, toJsonSchema) }; + + case 'UnionType': + return { anyOf: type.types.map(toJsonSchema) }; + + case 'IntersectionType': + return { allOf: type.types.map(toJsonSchema) }; + + case 'NumberType': + return { type: 'number' }; + + case 'StringType': + return { type: 'string' }; + } + } + + return { + type: 'object', + }; +}; diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts index 82b73d46da5c1..158d7ee7e76a3 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts @@ -260,7 +260,7 @@ describe('createApi', () => { body: { attributes: { _inspect: [] }, message: - 'Invalid value 1 supplied to : strict_keys/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)', + 'Invalid value 1 supplied to : Partial<{| query: Partial<{| _inspect: pipe(JSON, boolean) |}> |}>/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)', }, statusCode: 400, }); From 9c2605398ff96882777a9425033327a12fc4f6c8 Mon Sep 17 00:00:00 2001 From: Poff Poffenberger Date: Mon, 28 Jun 2021 13:19:22 -0500 Subject: [PATCH 07/74] [Canvas] Improvements to datasource expressions including SQL parameter support and array leniency (#99549) * Remove es sql strategy from behind Labs project, remove legacy essql code, remove last spot of legacy elasticsearch client from canvas * clean up test * fix es field test * remove comment Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/presentation_util/common/labs.ts | 19 +- .../functions/server/escount.ts | 79 --------- .../functions/server/esdocs.ts | 114 ------------ .../functions/server/essql.ts | 61 ------- .../functions/server/index.ts | 5 +- .../canvas/public/services/expressions.ts | 12 +- .../canvas/server/lib/essql_strategy.test.ts | 165 ++++++++++++++++++ .../canvas/server/lib/essql_strategy.ts | 9 +- .../canvas/server/lib/query_es_sql.test.ts | 111 ------------ .../plugins/canvas/server/lib/query_es_sql.ts | 105 ----------- x-pack/plugins/canvas/server/plugin.ts | 3 +- .../server/routes/es_fields/es_fields.test.ts | 74 ++++---- .../server/routes/es_fields/es_fields.ts | 6 +- .../server/routes/functions/functions.ts | 9 +- x-pack/plugins/canvas/server/routes/index.ts | 3 +- 15 files changed, 219 insertions(+), 556 deletions(-) delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts delete mode 100644 x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts create mode 100644 x-pack/plugins/canvas/server/lib/essql_strategy.test.ts delete mode 100644 x-pack/plugins/canvas/server/lib/query_es_sql.test.ts delete mode 100644 x-pack/plugins/canvas/server/lib/query_es_sql.ts diff --git a/src/plugins/presentation_util/common/labs.ts b/src/plugins/presentation_util/common/labs.ts index 7ca5272daa9c7..d80624fe0bb99 100644 --- a/src/plugins/presentation_util/common/labs.ts +++ b/src/plugins/presentation_util/common/labs.ts @@ -9,11 +9,10 @@ import { i18n } from '@kbn/i18n'; export const LABS_PROJECT_PREFIX = 'labs:'; -export const USE_DATA_SERVICE = `${LABS_PROJECT_PREFIX}canvas:useDataService` as const; export const TIME_TO_PRESENT = `${LABS_PROJECT_PREFIX}presentation:timeToPresent` as const; export const DEFER_BELOW_FOLD = `${LABS_PROJECT_PREFIX}dashboard:deferBelowFold` as const; -export const projectIDs = [TIME_TO_PRESENT, USE_DATA_SERVICE, DEFER_BELOW_FOLD] as const; +export const projectIDs = [TIME_TO_PRESENT, DEFER_BELOW_FOLD] as const; export const environmentNames = ['kibana', 'browser', 'session'] as const; export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const; @@ -35,22 +34,6 @@ export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = { }), solutions: ['canvas'], }, - [USE_DATA_SERVICE]: { - id: USE_DATA_SERVICE, - isActive: true, - isDisplayed: true, - environments: ['kibana', 'browser', 'session'], - name: i18n.translate('presentationUtil.experiments.enableUseDataServiceExperimentName', { - defaultMessage: 'Use data service', - }), - description: i18n.translate( - 'presentationUtil.experiments.enableUseDataServiceExperimentDescription', - { - defaultMessage: 'An experiment of using the new data.search service for Canvas datasources', - } - ), - solutions: ['canvas'], - }, [DEFER_BELOW_FOLD]: { id: DEFER_BELOW_FOLD, isActive: false, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts deleted file mode 100644 index 95f5ef446a470..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - ExpressionFunctionDefinition, - ExpressionValueFilter, -} from 'src/plugins/expressions/common'; -// @ts-expect-error untyped local -import { buildESRequest } from '../../../common/lib/request/build_es_request'; - -import { getFunctionHelp } from '../../../i18n'; - -interface Arguments { - index: string | null; - query: string; -} - -export function escount(): ExpressionFunctionDefinition< - 'escount', - ExpressionValueFilter, - Arguments, - any -> { - const { help, args: argHelp } = getFunctionHelp().escount; - - return { - name: 'escount', - type: 'number', - context: { - types: ['filter'], - }, - help, - args: { - query: { - types: ['string'], - aliases: ['_', 'q'], - help: argHelp.query, - default: '"-_index:.kibana"', - }, - index: { - types: ['string'], - default: '_all', - help: argHelp.index, - }, - }, - fn: (input, args, handlers) => { - input.and = input.and.concat([ - { - type: 'filter', - filterType: 'luceneQueryString', - query: args.query, - and: [], - }, - ]); - - const esRequest = buildESRequest( - { - index: args.index, - body: { - query: { - bool: { - must: [{ match_all: {} }], - }, - }, - }, - }, - input - ); - - return ((handlers as any) as { elasticsearchClient: any }) - .elasticsearchClient('count', esRequest) - .then((resp: { count: number }) => resp.count); - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts deleted file mode 100644 index e77717d689f95..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts +++ /dev/null @@ -1,114 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import squel from 'safe-squel'; -import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; -/* eslint-disable */ -import { queryEsSQL } from '../../../server/lib/query_es_sql'; -/* eslint-enable */ -import { ExpressionValueFilter } from '../../../types'; -import { getFunctionHelp } from '../../../i18n'; - -interface Arguments { - index: string; - query: string; - sort: string; - fields: string; - metaFields: string; - count: number; -} - -export function esdocs(): ExpressionFunctionDefinition< - 'esdocs', - ExpressionValueFilter, - Arguments, - any -> { - const { help, args: argHelp } = getFunctionHelp().esdocs; - - return { - name: 'esdocs', - type: 'datatable', - context: { - types: ['filter'], - }, - help, - args: { - query: { - types: ['string'], - aliases: ['_', 'q'], - help: argHelp.query, - default: '-_index:.kibana', - }, - count: { - types: ['number'], - default: 1000, - help: argHelp.count, - }, - fields: { - help: argHelp.fields, - types: ['string'], - }, - index: { - types: ['string'], - default: '_all', - help: argHelp.index, - }, - // TODO: This arg isn't being used in the function. - // We need to restore this functionality or remove it as an arg. - metaFields: { - help: argHelp.metaFields, - types: ['string'], - }, - sort: { - types: ['string'], - help: argHelp.sort, - }, - }, - fn: (input, args, context) => { - const { count, index, fields, sort } = args; - - input.and = input.and.concat([ - { - type: 'filter', - filterType: 'luceneQueryString', - query: args.query, - and: [], - }, - ]); - - let query = squel.select({ - autoQuoteTableNames: true, - autoQuoteFieldNames: true, - autoQuoteAliasNames: true, - nameQuoteCharacter: '"', - }); - - if (index) { - query.from(index); - } - - if (fields) { - const allFields = fields.split(',').map((field) => field.trim()); - allFields.forEach((field) => (query = query.field(field))); - } - - if (sort) { - const [sortField, sortOrder] = sort.split(',').map((str) => str.trim()); - if (sortField) { - query.order(`"${sortField}"`, sortOrder === 'asc'); - } - } - - return queryEsSQL(((context as any) as { elasticsearchClient: any }).elasticsearchClient, { - count, - query: query.toString(), - filter: input.and, - }); - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts deleted file mode 100644 index d14a8c0bec762..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -/* eslint-disable */ -import { queryEsSQL } from '../../../server/lib/query_es_sql'; -/* eslint-enable */ -import { ExpressionValueFilter } from '../../../types'; -import { getFunctionHelp } from '../../../i18n'; - -interface Arguments { - query: string; - count: number; - timezone: string; -} - -export function essql(): ExpressionFunctionDefinition< - 'essql', - ExpressionValueFilter, - Arguments, - any -> { - const { help, args: argHelp } = getFunctionHelp().essql; - - return { - name: 'essql', - type: 'datatable', - context: { - types: ['filter'], - }, - help, - args: { - query: { - aliases: ['_', 'q'], - types: ['string'], - help: argHelp.query, - }, - count: { - types: ['number'], - help: argHelp.count, - default: 1000, - }, - timezone: { - aliases: ['tz'], - types: ['string'], - default: 'UTC', - help: argHelp.timezone, - }, - }, - fn: (input, args, context) => { - return queryEsSQL(((context as any) as { elasticsearchClient: any }).elasticsearchClient, { - ...args, - filter: input.and, - }); - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/index.ts index e9731448f65b7..ae3778366651c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/index.ts @@ -6,9 +6,6 @@ */ import { demodata } from './demodata'; -import { escount } from './escount'; -import { esdocs } from './esdocs'; import { pointseries } from './pointseries'; -import { essql } from './essql'; -export const functions = [demodata, esdocs, escount, essql, pointseries]; +export const functions = [demodata, pointseries]; diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts index 35493341e0e88..219edb667efc6 100644 --- a/x-pack/plugins/canvas/public/services/expressions.ts +++ b/x-pack/plugins/canvas/public/services/expressions.ts @@ -24,10 +24,6 @@ export const expressionsServiceFactory: CanvasServiceFactory const loadServerFunctionWrappers = async () => { if (!cached) { cached = (async () => { - const labService = startPlugins.presentationUtil.labsService; - const hasDataSearch = labService.isProjectEnabled('labs:canvas:useDataService'); - const dataSearchFns = ['essql', 'esdocs', 'escount']; - const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS); const batchedFunction = bfetch.batchedFunction({ url: API_ROUTE_FUNCTIONS }); const { serialize } = serializeProvider(expressions.getTypes()); @@ -36,13 +32,7 @@ export const expressionsServiceFactory: CanvasServiceFactory // function that matches its definition, but which simply // calls the server-side function endpoint. Object.keys(serverFunctionList).forEach((functionName) => { - // Allow function to be overwritten if we want to use - // the server-hosted essql, esdocs, and escount functions - if (dataSearchFns.includes(functionName)) { - if (hasDataSearch && expressions.getFunction(functionName)) { - return; - } - } else if (expressions.getFunction(functionName)) { + if (expressions.getFunction(functionName)) { return; } diff --git a/x-pack/plugins/canvas/server/lib/essql_strategy.test.ts b/x-pack/plugins/canvas/server/lib/essql_strategy.test.ts new file mode 100644 index 0000000000000..7ef543e848add --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/essql_strategy.test.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { essqlSearchStrategyProvider } from './essql_strategy'; +import { EssqlSearchStrategyRequest } from '../../types'; +import { zipObject } from 'lodash'; + +const getMockEssqlResponse = () => ({ + body: { + columns: [ + { name: 'One', type: 'keyword' }, + { name: 'Two', type: 'keyword' }, + ], + rows: [ + ['foo', 'bar'], + ['buz', 'baz'], + ['beep', 'boop'], + ], + cursor: 'cursor-value', + }, + statusCode: 200, +}); + +const basicReq: EssqlSearchStrategyRequest = { + query: 'SELECT * FROM my_index;', + count: 3, + params: ['my_var'], + filter: [ + { + type: 'filter', + filterType: 'exactly', + value: 'Test Value', + column: 'One', + and: [], + }, + ], + timezone: 'UTC', +}; + +describe('ESSQL search strategy', () => { + describe('strategy interface', () => { + it('returns a strategy with a `search` function', async () => { + const essqlSearch = await essqlSearchStrategyProvider(); + expect(typeof essqlSearch.search).toBe('function'); + }); + }); + + describe('search()', () => { + let mockQuery: jest.Mock; + let mockClearCursor: jest.Mock; + let mockDeps: any; + + beforeEach(() => { + mockQuery = jest.fn().mockResolvedValueOnce(getMockEssqlResponse()); + mockClearCursor = jest.fn(); + mockDeps = ({ + esClient: { + asCurrentUser: { + sql: { + query: mockQuery, + clearCursor: mockClearCursor, + }, + }, + }, + } as unknown) as any; + }); + + describe('query functionality', () => { + it('performs a simple query', async () => { + const sqlSearch = await essqlSearchStrategyProvider(); + const result = await sqlSearch.search(basicReq, {}, mockDeps).toPromise(); + const [[request]] = mockQuery.mock.calls; + + expect(request.format).toEqual('json'); + expect(request.body).toEqual( + expect.objectContaining({ + query: basicReq.query, + client_id: 'canvas', + fetch_size: basicReq.count, + time_zone: basicReq.timezone, + field_multi_value_leniency: true, + params: ['my_var'], + }) + ); + + const expectedColumns = getMockEssqlResponse().body.columns.map((c) => ({ + id: c.name, + name: c.name, + meta: { type: 'string' }, + })); + const columnNames = expectedColumns.map((c) => c.name); + const expectedRows = getMockEssqlResponse().body.rows.map((r) => zipObject(columnNames, r)); + + expect(result.columns).toEqual(expectedColumns); + expect(result.rows).toEqual(expectedRows); + }); + + it('iterates over cursor to retrieve for records query', async () => { + const pageOne = { + body: { + columns: [ + { name: 'One', type: 'keyword' }, + { name: 'Two', type: 'keyword' }, + ], + rows: [['foo', 'bar']], + cursor: 'cursor-value', + }, + }; + + const pageTwo = { + body: { + rows: [['buz', 'baz']], + }, + }; + + mockQuery.mockReset().mockReturnValueOnce(pageOne).mockReturnValueOnce(pageTwo); + + const sqlSearch = await essqlSearchStrategyProvider(); + const result = await sqlSearch.search({ ...basicReq, count: 2 }, {}, mockDeps).toPromise(); + + expect(result.rows).toHaveLength(2); + }); + + it('closes any cursors that remain open', async () => { + const sqlSearch = await essqlSearchStrategyProvider(); + await sqlSearch.search(basicReq, {}, mockDeps).toPromise(); + + const [[cursorReq]] = mockClearCursor.mock.calls; + expect(cursorReq.body.cursor).toEqual('cursor-value'); + }); + + it('emits an error if the client throws', async () => { + const req: EssqlSearchStrategyRequest = { + query: 'SELECT * FROM my_index;', + count: 1, + params: [], + filter: [ + { + type: 'filter', + filterType: 'exactly', + value: 'Test Value', + column: 'category.keyword', + and: [], + }, + ], + timezone: 'UTC', + }; + + expect.assertions(1); + mockQuery.mockReset().mockRejectedValueOnce(new Error('client error')); + const eqlSearch = await essqlSearchStrategyProvider(); + eqlSearch.search(req, {}, mockDeps).subscribe( + () => {}, + (err) => { + expect(err).toEqual(new Error('client error')); + } + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/canvas/server/lib/essql_strategy.ts b/x-pack/plugins/canvas/server/lib/essql_strategy.ts index 795b4fedaaaab..273ffb53b0ed2 100644 --- a/x-pack/plugins/canvas/server/lib/essql_strategy.ts +++ b/x-pack/plugins/canvas/server/lib/essql_strategy.ts @@ -8,7 +8,7 @@ import { from } from 'rxjs'; import { map, zipObject } from 'lodash'; -import { ISearchStrategy, PluginStart } from 'src/plugins/data/server'; +import { ISearchStrategy } from 'src/plugins/data/server'; import { getKbnServerError } from '../../../../../src/plugins/kibana_utils/server'; import { EssqlSearchStrategyRequest, EssqlSearchStrategyResponse } from '../../types'; @@ -17,9 +17,10 @@ import { buildBoolArray } from '../../common/lib/request/build_bool_array'; import { sanitizeName } from '../../common/lib/request/sanitize_name'; import { normalizeType } from '../../common/lib/request/normalize_type'; -export const essqlSearchStrategyProvider = ( - data: PluginStart -): ISearchStrategy => { +export const essqlSearchStrategyProvider = (): ISearchStrategy< + EssqlSearchStrategyRequest, + EssqlSearchStrategyResponse +> => { return { search: (request, options, { esClient }) => { const { count, query, filter, timezone, params } = request; diff --git a/x-pack/plugins/canvas/server/lib/query_es_sql.test.ts b/x-pack/plugins/canvas/server/lib/query_es_sql.test.ts deleted file mode 100644 index 5c07f70579f8d..0000000000000 --- a/x-pack/plugins/canvas/server/lib/query_es_sql.test.ts +++ /dev/null @@ -1,111 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { zipObject } from 'lodash'; -import { queryEsSQL } from './query_es_sql'; -// @ts-expect-error -import { buildBoolArray } from './build_bool_array'; - -const response = { - columns: [ - { name: 'One', type: 'keyword' }, - { name: 'Two', type: 'keyword' }, - ], - rows: [ - ['foo', 'bar'], - ['buz', 'baz'], - ], - cursor: 'cursor-value', -}; - -const baseArgs = { - count: 1, - query: 'query', - filter: [], - timezone: 'timezone', -}; - -const getApi = (resp = response) => { - const api = jest.fn(); - api.mockResolvedValue(resp); - return api; -}; - -describe('query_es_sql', () => { - it('should call the api with the given args', async () => { - const api = getApi(); - - queryEsSQL(api, baseArgs); - - expect(api).toHaveBeenCalled(); - const givenArgs = api.mock.calls[0][1]; - - expect(givenArgs.body.fetch_size).toBe(baseArgs.count); - expect(givenArgs.body.query).toBe(baseArgs.query); - expect(givenArgs.body.time_zone).toBe(baseArgs.timezone); - }); - - it('formats the response', async () => { - const api = getApi(); - - const result = await queryEsSQL(api, baseArgs); - - const expectedColumns = response.columns.map((c) => ({ - id: c.name, - name: c.name, - meta: { type: 'string' }, - })); - const columnNames = expectedColumns.map((c) => c.name); - const expectedRows = response.rows.map((r) => zipObject(columnNames, r)); - - expect(result.type).toBe('datatable'); - expect(result.columns).toEqual(expectedColumns); - expect(result.rows).toEqual(expectedRows); - }); - - it('fetches pages until it has the requested count', async () => { - const pageOne = { - columns: [ - { name: 'One', type: 'keyword' }, - { name: 'Two', type: 'keyword' }, - ], - rows: [['foo', 'bar']], - cursor: 'cursor-value', - }; - - const pageTwo = { - rows: [['buz', 'baz']], - }; - - const api = getApi(pageOne); - api.mockReturnValueOnce(pageOne).mockReturnValueOnce(pageTwo); - - const result = await queryEsSQL(api, { ...baseArgs, count: 2 }); - expect(result.rows).toHaveLength(2); - }); - - it('closes any cursors that remain open', async () => { - const api = getApi(); - - await queryEsSQL(api, baseArgs); - expect(api.mock.calls[1][1].body.cursor).toBe(response.cursor); - }); - - it('throws on errors', async () => { - const api = getApi(); - api.mockRejectedValueOnce(new Error('parsing_exception')); - api.mockRejectedValueOnce(new Error('generic es error')); - - expect(queryEsSQL(api, baseArgs)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Couldn't parse Elasticsearch SQL query. You may need to add double quotes to names containing special characters. Check your query and try again. Error: parsing_exception"` - ); - - expect(queryEsSQL(api, baseArgs)).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unexpected error from Elasticsearch: generic es error"` - ); - }); -}); diff --git a/x-pack/plugins/canvas/server/lib/query_es_sql.ts b/x-pack/plugins/canvas/server/lib/query_es_sql.ts deleted file mode 100644 index 2c4416094914d..0000000000000 --- a/x-pack/plugins/canvas/server/lib/query_es_sql.ts +++ /dev/null @@ -1,105 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { map, zipObject } from 'lodash'; -import { buildBoolArray } from '../../common/lib/request/build_bool_array'; -import { sanitizeName } from '../../common/lib/request/sanitize_name'; -import { normalizeType } from '../../common/lib/request/normalize_type'; -import { LegacyAPICaller } from '../../../../../src/core/server'; -import { ExpressionValueFilter } from '../../types'; - -interface Args { - count: number; - query: string; - timezone?: string; - filter: ExpressionValueFilter[]; -} - -interface CursorResponse { - cursor?: string; - rows: string[][]; -} - -type QueryResponse = CursorResponse & { - columns: Array<{ - name: string; - type: string; - }>; - cursor?: string; - rows: string[][]; -}; - -export const queryEsSQL = async ( - elasticsearchClient: LegacyAPICaller, - { count, query, filter, timezone }: Args -) => { - try { - let response: QueryResponse = await elasticsearchClient('transport.request', { - path: '/_sql?format=json', - method: 'POST', - body: { - query, - time_zone: timezone, - fetch_size: count, - client_id: 'canvas', - filter: { - bool: { - must: [{ match_all: {} }, ...buildBoolArray(filter)], - }, - }, - }, - }); - - const columns = response.columns.map(({ name, type }) => { - return { - id: sanitizeName(name), - name: sanitizeName(name), - meta: { type: normalizeType(type) }, - }; - }); - const columnNames = map(columns, 'name'); - let rows = response.rows.map((row) => zipObject(columnNames, row)); - - while (rows.length < count && response.cursor !== undefined) { - response = await elasticsearchClient('transport.request', { - path: '/_sql?format=json', - method: 'POST', - body: { - cursor: response.cursor, - }, - }); - - rows = [...rows, ...response.rows.map((row) => zipObject(columnNames, row))]; - } - - if (response.cursor !== undefined) { - elasticsearchClient('transport.request', { - path: '/_sql/close', - method: 'POST', - body: { - cursor: response.cursor, - }, - }); - } - - return { - type: 'datatable', - meta: { - type: 'essql', - }, - columns, - rows, - }; - } catch (e) { - if (e.message.indexOf('parsing_exception') > -1) { - throw new Error( - `Couldn't parse Elasticsearch SQL query. You may need to add double quotes to names containing special characters. Check your query and try again. Error: ${e.message}` - ); - } - throw new Error(`Unexpected error from Elasticsearch: ${e.message}`); - } -}; diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts index 9ccf3c251fecc..f0b7c0243000f 100644 --- a/x-pack/plugins/canvas/server/plugin.ts +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -61,7 +61,6 @@ export class CanvasPlugin implements Plugin { router: canvasRouter, expressions: plugins.expressions, bfetch: plugins.bfetch, - elasticsearch: coreSetup.elasticsearch, logger: this.logger, }); @@ -77,7 +76,7 @@ export class CanvasPlugin implements Plugin { setupInterpreter(plugins.expressions); coreSetup.getStartServices().then(([_, depsStart]) => { - const strategy = essqlSearchStrategyProvider(depsStart.data); + const strategy = essqlSearchStrategyProvider(); plugins.data.search.registerSearchStrategy(ESSQL_SEARCH_STRATEGY, strategy); }); } diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index 1e95ee809e767..02d7e9ca2e269 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -13,7 +13,7 @@ import { getMockedRouterDeps } from '../test_helpers'; const mockRouteContext = ({ core: { elasticsearch: { - legacy: { client: elasticsearchServiceMock.createLegacyScopedClusterClient() }, + client: elasticsearchServiceMock.createScopedClusterClient(), }, }, } as unknown) as RequestHandlerContext; @@ -33,27 +33,29 @@ describe('Retrieve ES Fields', () => { it(`returns 200 with fields from existing index/index pattern`, async () => { const index = 'test'; const mockResults = { - indices: ['test'], - fields: { - '@timestamp': { - date: { - type: 'date', - searchable: true, - aggregatable: true, + body: { + indices: ['test'], + fields: { + '@timestamp': { + date: { + type: 'date', + searchable: true, + aggregatable: true, + }, }, - }, - name: { - text: { - type: 'text', - searchable: true, - aggregatable: false, + name: { + text: { + type: 'text', + searchable: true, + aggregatable: false, + }, }, - }, - products: { - object: { - type: 'object', - searchable: false, - aggregatable: false, + products: { + object: { + type: 'object', + searchable: false, + aggregatable: false, + }, }, }, }, @@ -66,10 +68,10 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock; + const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser + .fieldCaps as jest.Mock; - callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + fieldCapsMock.mockResolvedValueOnce(mockResults); const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); @@ -85,7 +87,7 @@ describe('Retrieve ES Fields', () => { it(`returns 200 with empty object when index/index pattern has no fields`, async () => { const index = 'test'; - const mockResults = { indices: [index], fields: {} }; + const mockResults = { body: { indices: [index], fields: {} } }; const request = httpServerMock.createKibanaRequest({ method: 'get', path, @@ -94,10 +96,10 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock; + const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser + .fieldCaps as jest.Mock; - callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + fieldCapsMock.mockResolvedValueOnce(mockResults); const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); @@ -109,8 +111,10 @@ describe('Retrieve ES Fields', () => { const index = 'test'; const mockResults = { - indices: [index], - fields: {}, + body: { + indices: [index], + fields: {}, + }, }; const request = httpServerMock.createKibanaRequest({ @@ -122,10 +126,10 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock; + const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser + .fieldCaps as jest.Mock; - callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + fieldCapsMock.mockResolvedValueOnce(mockResults); const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); @@ -142,10 +146,10 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock; + const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser + .fieldCaps as jest.Mock; - callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); + fieldCapsMock.mockRejectedValueOnce(new Error('Index not found')); await expect( routeHandler(mockRouteContext, request, kibanaResponseFactory) diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts index 20a4775847c91..340a6cdb902ff 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts @@ -28,7 +28,7 @@ export function initializeESFieldsRoute(deps: RouteInitializerDeps) { }, }, catchErrorHandler(async (context, request, response) => { - const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; + const client = context.core.elasticsearch.client.asCurrentUser; const { index, fields } = request.query; const config = { @@ -36,8 +36,8 @@ export function initializeESFieldsRoute(deps: RouteInitializerDeps) { fields: fields || '*', }; - const esFields = await callAsCurrentUser('fieldCaps', config).then((resp) => { - return mapValues(resp.fields, (types) => { + const esFields = await client.fieldCaps(config).then((resp) => { + return mapValues(resp.body.fields, (types) => { if (keys(types).length > 1) { return 'conflict'; } diff --git a/x-pack/plugins/canvas/server/routes/functions/functions.ts b/x-pack/plugins/canvas/server/routes/functions/functions.ts index af449cf785228..ed1559034c3a6 100644 --- a/x-pack/plugins/canvas/server/routes/functions/functions.ts +++ b/x-pack/plugins/canvas/server/routes/functions/functions.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { LegacyAPICaller } from 'src/core/server'; import { serializeProvider } from '../../../../../../src/plugins/expressions/common'; import { RouteInitializerDeps } from '../'; import { API_ROUTE_FUNCTIONS } from '../../../common/lib/constants'; @@ -34,12 +33,9 @@ export function initializeGetFunctionsRoute(deps: RouteInitializerDeps) { } export function initializeBatchFunctionsRoute(deps: RouteInitializerDeps) { - const { bfetch, elasticsearch, expressions } = deps; + const { bfetch, expressions } = deps; - async function runFunction( - handlers: { environment: string; elasticsearchClient: LegacyAPICaller }, - fnCall: FunctionCall - ) { + async function runFunction(handlers: { environment: string }, fnCall: FunctionCall) { const { functionName, args, context } = fnCall; const { deserialize } = serializeProvider(expressions.getTypes()); @@ -61,7 +57,6 @@ export function initializeBatchFunctionsRoute(deps: RouteInitializerDeps) { onBatchItem: async (fnCall: FunctionCall) => { const handlers = { environment: 'server', - elasticsearchClient: elasticsearch.legacy.client.asScoped(request).callAsCurrentUser, }; const result = await runFunction(handlers, fnCall); if (typeof result === 'undefined') { diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts index e42b47618ed65..ccc8f7e278266 100644 --- a/x-pack/plugins/canvas/server/routes/index.ts +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IRouter, Logger, ElasticsearchServiceSetup } from 'src/core/server'; +import { IRouter, Logger } from 'src/core/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { initCustomElementsRoutes } from './custom_elements'; @@ -20,7 +20,6 @@ export interface RouteInitializerDeps { logger: Logger; expressions: ExpressionsServerSetup; bfetch: BfetchServerSetup; - elasticsearch: ElasticsearchServiceSetup; } export function initRoutes(deps: RouteInitializerDeps) { From d1c3183ca7836f30dcc025fdcaec39b5d84a70d5 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 28 Jun 2021 11:21:54 -0700 Subject: [PATCH 08/74] Update jest.sh file to strip leading './' dir (#103507) --- x-pack/plugins/enterprise_search/jest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/enterprise_search/jest.sh b/x-pack/plugins/enterprise_search/jest.sh index 8bc3134a62d8e..b08459827fa7d 100644 --- a/x-pack/plugins/enterprise_search/jest.sh +++ b/x-pack/plugins/enterprise_search/jest.sh @@ -5,6 +5,8 @@ TARGET="${1:-all}" if [[ $TARGET && $TARGET != "all" ]] then + # Strip any leading ./ + TARGET=${TARGET#./} # If this is a file if [[ "$TARGET" == *".ts"* ]]; then PATH_WITHOUT_EXTENSION=${1%%.*} From 62e79ffaae39c63adb4069272cf2d6944f3fdb72 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 28 Jun 2021 20:22:49 +0200 Subject: [PATCH 09/74] [RFC] Node.js clustering in Kibana (#94057) Co-authored-by: Luke Elmers --- rfcs/images/15_clustering/cluster-mode.png | Bin 0 -> 132797 bytes rfcs/images/15_clustering/no-cluster-mode.png | Bin 0 -> 49301 bytes rfcs/images/15_clustering/perf-4-workers.png | Bin 0 -> 446234 bytes .../perf-clustering-2-worker.png | Bin 0 -> 450498 bytes .../15_clustering/perf-no-clustering.png | Bin 0 -> 452150 bytes rfcs/text/0020_nodejs_clustering.md | 729 ++++++++++++++++++ 6 files changed, 729 insertions(+) create mode 100644 rfcs/images/15_clustering/cluster-mode.png create mode 100644 rfcs/images/15_clustering/no-cluster-mode.png create mode 100644 rfcs/images/15_clustering/perf-4-workers.png create mode 100644 rfcs/images/15_clustering/perf-clustering-2-worker.png create mode 100644 rfcs/images/15_clustering/perf-no-clustering.png create mode 100644 rfcs/text/0020_nodejs_clustering.md diff --git a/rfcs/images/15_clustering/cluster-mode.png b/rfcs/images/15_clustering/cluster-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..dad24c42f7108771cc5199af65da06f27ed63cf9 GIT binary patch literal 132797 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 B#XbN4 literal 0 HcmV?d00001 diff --git a/rfcs/images/15_clustering/no-cluster-mode.png b/rfcs/images/15_clustering/no-cluster-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..f2f12428077be22e851d57117d3857ad6f784dae GIT binary patch literal 49301 zcmeFZWmMJQ)&>d)QX+_i($Wng(%s$NC?MUPf`ov8NJ$BTbV;-6l#=f5kd$t?YlEKi zKj*&p!~5}$@iGRo_b*n=HP?)1KFeSQIdL=;JQNrh7&J)<5hWOydyX(La5zZ!!4=}E zK0O#1lxZ_zVFgKHVG;#<8xu22V;C5T;Fvf>6=gG=w>Kw)_zchBl!R>j9N{F8dEXA^ zW&4wVRCpyJ7(z<=j*J;cMdbU3_3Zb4>0~6Y1?k7{w*`6$+($6RMrBhthL!#t&s*bh z{N-THdu{c)vi9I+cN!6f!k-N1O(QLgQHar}_b@hMI-YR%#?TE?k=Gj$I2@{!p3~9O z!;`QS+|Uy5z?llol_b1B+P%3^S6tf5(1A%6@sDy-vZidd>cCqKp~p1`1>8Jg^;yvn?$@Gc?c3yR8} +G*rkgO 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

1cR+TPgM+QHPuks0c^53BSQ8N_{M-5pSuGcnJ zjD|)wuZ$U8t!$y~!0@( z2;0~jldv%|GcuF%p^%V}@Yoxfa4Ct1{dpaH<0Un9bhPDSVsdeDVRU)HXk-6|={YAS zClfOZ6AKFic!I&f&Dzn>mBHG9?5>f&?T8pVytX&9bu_cFCV{qV_{zq~k(ZPd+R?v$ z?#5~CYWDA*tR4PL3rvs+dWY#bBQw*#+6J%kK(BHsn7JBTYKoXy0hxh5_+GwX<+*+S z|8?izJ^tfOjeoz%!SNq&{^QPnzp3hAY%gqM1-f+P`}cDF>Gq#D|Gdb<1fBUmBylI^ z+p9p%d?-9j|5`IXlo=#>HL#2XW+L(`;4e@z=npI^_=o!LFZ8q4upRl=To@Pu7)cR9 z6<64;WaMd`p|kcBB+^zji&t|oS{5{T(vc!Gep=F7H7eWY(veufUg{o-D(}aIFfyD~ zFNhw6q+KFBBe%a`cfUrJ}4LtLq6Ur+6tOUfEgmiF?eND>%W zBmo#WjJIGzLO-VdLUI)Df2o_IX!sx3Vc@>N_=O|F{`*PjO9D}t0{t(|+7+$->tQ6W zFaOyt44kYKjNsTeLhsUpHcl8;M>%tr+)|f=TiSS zcnn9`Njnw%{~Q?@?3;f~>;DFOC&~Yl?EmR3*na<~2LHcN11yr?S0{5*WoAQ`flp1o z@66O(SounGK7iWlXTbKMHOYPbYrTz<8^1|LSuS@(I*+KTPvqtmn%C^s*A&#BZ;Tdb zSLUlHp9{jkS&0BcES4;*{MQguCpV@?@R|vYwSy;7*vN==Z@+;n! z0=NcWyvG9lBTtwog#Q}eBzWt(Bef~xI9q|%hvagDrVg`U%q*l{Suy} z{}UJ>!$biq${*wGTZnPGnrO3?U{P3n?;!_CT-Y>Y&_hD=hRYQuhxTz_V5Is@;gG7u zPGDC4cJz@Gj@ak2Sl6}*!MTejYk8w$(mc;ikZMd?iOZKd#Pa;`*LAu9%cMH+Qg}{71$Arv=`f%0d)FpAPntQOt;DS*R1QkfO>|PFSZdqUIqf{* zzM&vlpoBg7m6bC2Sf0o5WZEGj44=Js)@_YY+jSwpYAxBNLKvNQ7>u9qBM_r1<=fOe z7KM^`e?8|JGSAKBf%D~F`&6>aY`n$Kl5R$ehMTKhtNv6$$m{a)0VU*f>V!-_EtY3n zE*U(UeV=H&^A!$zh1w|=Rr)%ksHRYvY7NG8U6R9ycqjSJ*VB!j^Kq?zH*CrGxw$6n zwFC25#{;f$?9`6^&RxU6F-XieArhC}L+9z&^SL_G*~HPdd`^61|9id(@_J~-Mk?7@hWG4? zY*)_hd@Nc`?cv(YcWb9;Hu>UaVnY*Uiz@kwL1gVgJMDy&0+G|OV#V4QkI6SFUM5;y zg0GbQ*n2Ru&a=OGogWsr;p$IQ%6vA^_qi&!YPdQ+4&uLTT8)46MJl(67sM%#z_3Vd zIS~FT!^NYIs0$Avr&^WFN#+z8hetsHV_x z3jN(Ft?)*{)rW)9SDp~p7t80agOXGeMLt)Xrwh5;-Dvwi@NLR?kH=dOpZuazyY`^Bl>d4!!VPVJhnaA5tVn-$&)ho_VJpA1ci&HGk(P0^ zxxOM{juQ2A>#${M&*}{0%0@N`MT+Ns@YNds&0dq#av%+^QU(#P`#5PR{VQpvy0R2} zsuS@Ux-l8u8=!gPVEe2T*wWT0e0m>MZsm)B^Sy_CZu)rAf{9=JA*#-^R|G)SjUZmt?QwiHg+(i&es+<2cDE$_cun+C|aW)t^Tj zBY7?22djRK(scTyI}1U?6)oue(_g%fHGMi`wXJ{QN~c&9LI$n(nxugS^G*5OTwU4U z>kL)-GEJ+l=VojpI_&O=`JNmY%wk}V!dB#e{mWo}X!O{vd-LXtTV}Gy4#e%{lu?xI z)+cGk=RFw~frmd|^oM>L>omWTJs|3s=~^uEkY(l}pUsb#RjEX-C_A)Q4hs%u@FwU<3P ze)=ZK5Le&RZl|p46Um^KMMg+G#-jd|f)9l5eq5W@2)~M!8lPxv?b&ML)iZ|oh3DNp zB^uRB;oP+da-sh1Q94`{=JNbkju(3oK3nOUqt$48`x8Mh@Pk0$gAiM+zjhZB8NT=v z!Sv=S`<&-%s5wP8T*TBC4mKP9b~NH0YyanL|cTlOPS6K?4)^+$9!XMH}CPwJhi=1&UE~@*O z?|iNgQoLGai=`=#PS~a_mkQjpPIY{>Fq|c&Jc9%Xk*aP|fmZ%86AEmwZ#YApr>xk+ z4%27rJjAoUxSKE2TksLHqchl#()-A*wX|ynUD{E3az(HCs~t{S6p02AxtlK2+d({< z>jElz2eCO}vAy=%a3T9q@!ZdFAxPno4uT(&V(6J{aJQdNy3Bczm5U$w9F3@xQI*+` zaYW*Bx`?Luu*{vF^zq;D*psr_4-h+?;N+Hu*FancfNxj(-n&;zH{s&PWv|wzqpRcC z+Ywy*snkAgtWvx-Nh35#qk9G0CJwa(lhb8*{rX}zVj9moQrr9MGD{T(a93Qgb%JET zTi)$B-x6e=)A@IiiPACn%iMAG9S{jIMyc1!Lc43>?T9y%`o=M;r0nu^>R&{nwJ*S| z(>pW^m3E`D?RSDBOViE@>e~-uvTX;RlC3372$FbRY*!8d&xc|4z3IN&N$c*IHwF(= z-J#O*XhOH{CcWYS@b5`^Hk0A4-&*5a4SDo~NrIoT?=q@lp*>+H`E*(!>V9)|lAa#U zMXOF+DVBu$b0v0#{=42dC!O(fngtG)vw3={ff$*1I6ZA)H$%$iGZ^1RU_;u@Y;pch z2@amfW2<=m4gZCv9J2&QXIBPnT_Rl^HxVw!q)TDkziS;Tx=tgig!B%WDc0N7>mhW7@24^0B103N7StV2y7_f5 zz+%jJUu+{B>~tXqPd%37cs3TEa&x&>Zxyq8=5ur6GXNYjmQ~q+m|m)sLHikk9^<;y zvW^AWnmb-6ixk*$=F(vIY1c1hF%A06iZMF>O=i0Om7bQ|c z@S`tJ4l@SR-1=H)dG*GvJ1Q{_m(a0aa6lASH`>Ju!`%)0bPg9e7MNx#d1hK4&5ehg z%O54IL!WhG<~VmK;cSq7-N2zkk5h5VE0O11iPq7?jKYl7mBswN^dl_vc>Faw(<@ZS z>%!t6c#+y;4;9B$=cmzq<&a2#UbdS3-Fkz_JlCg7k=mBrR8RaQkhPKVgTD#ByDth2 zh;f}4rp@yTaG*gC-x4s@1KM-Vknc{R<`)pl$5e7MMQ^!jH)r?SiFXT}7V6K|Quw*{ zS|5`I^FgEnmnOR=sn`{9o1TB7%j9FseeDS13o#H-y;)9E62rHyGMwdQ#feWH0kM`TBv zpTGX0Qa~N7*)xP1D~?>#UkC>F-NR*!xw6DOAnFltMr3bOyT2MB{#90cqz`?#hVK)>D?fg9`64t ze4z8x7?$9rxyl!gsLz{Il{Vz+17%qI!DD1w-5czN&HlAhSmUCM?>pTbs>OxW4t_IM z&HJqb7rm#(51|dbY-U6HQu`tAuuYWYi1FK3dIWYwUGqrtFUDpn%=kxM)XLhL5--I+ z!NVdA2g3LjvgveU-;KL}AAZ@KIo_9-nnYt@OB;eEiL&gW*J6e^o@#slu4#b|n z1s#7rZ(r`ELD#(g;(czG%t+H2qsW-@)f0kCtCcxT)AGFM>IyNDNUcw%o9xJ*n zU?l?NFr&2|FU4gJ&V;U4tY41F`07lvpt+r?ZHDfvrp781WyI-C_N{Hw`xIxlrmc?7 z9vo0n5GgjD4xo)F>KPtlcW8b8RaX+N20C<(66pk%_EBq$6#?}0>Bp;+~^F-nz9 zH%E#S#n!AP{0g1J?|ZSesf7?3Nlnk5V+pY{*QW36Q3f)V5#1b4 zsuq$w>y)Cg$kmZF_LW&uUH2v&<6TTMrWb-(@7eLs6_0x*fvkm=3M4Q|gf4&L)XCVl z_ivzqu5~}r{as9!l4xi$NH}g9{|;MCqiK$8fGkc5IqF;L5NB|)QmcPqr0wvz{nv++ zU3j=DQ<*R4SZx^N9UJ^gO?$O3y^#eFgd=~Wv@c0N=xF=>ElRE{S-ECKE88ug0ptMo zO+tit6Q*8c%8Fl&_BfZ=ukf|+@83x*8N2&}6|4*wSD}X9N7wz+;ENB~~WB9sW zM-WRg^9EX;h@!GL&KW;P6ugx@> zS94D2Bks!Ai2BWR2YZN$%D~*^Dm!F9THdqX2x7JB4q{jb5MK5^wL|!u03yRlqQ>0c z!F=ekU9L`E92aY}Jppng`*&9<*N0gHr1J?ou4MOYVy0KbQk>mzWQ=6f}BS<5!NzF)cP>**t`rb=tpLK1)%_<@PsZyl0uSldNM3Ntd zyTM~NNIuug94BY-GkJNnx9)r+Z+0K6Py3Y;tSAa3IFN^KhBD6keH`JOdP+x+vcq{< zukK~iBsFeN8F_ccG@Y>(eVK+UPm$0Craxw!*2dS*jq-W-+JV&fLAucO<_F>5yodwN z-h+U}=zS%O*>A?C0ZpaDR^f-7tu@ch=a~d?^(W_{`J7t#cBFfE8pFeJ`x0gCTkTqr$*FF52dd*yjo#syuRzh4lnQ@(3U-xwFsKrJl)kE_E|C)K*SrlqDI0;%7n18|zO0>7(9r(_ST0x%C7Gnj_=v{IYN7oUkkWv@OUaiOhz6 zoPwr|?imnH&(+n2cvP8Jy^E`*7^v*H1Nv)zou3oc*QTiDx`b1Ngt|9LW`9D| zGF54IKPT3h+B95M*rcF3FJ4UaBp+U_`e!zb2Jb!N$Z? z4`R7;5E$eUYTN&2y)a*lZR`E7@uz@BXlIG_9ktnb8f0M zc`Q-skYG7j2VSRN>D9T%Yh%nzS38YX+UPUYE{gq;(qJG@tB=aaf>j9gTF%UA^FgAv8Zl*#YCiTgCo;n{fnOs3K za(TK?>op>L#u2Al1M=N1kn2AY^d6 z2v7Kn8}=K(VahXNWt*mYF0 z4=MlsQ?!D@cdq zL$}rooD%v*HBlC2wIO2WdrR`WI@U68Bd7 zqNgvYW~-#zQvuft{a{td7R%~LvGH+Fdz7m-$O^n{<0xV^($;I6H%4Qp!46j=0gVLc zeM9>@5To$M;T+|wBuMaMs&k~+5TqO!8kF_4M#eU{W~0LADoAh`9rOTeG^q&;`B(mS zmj+PF0W+6`{^l>5^Jmi;#lrY8mg)+({Y9bvT*m@&23>DZ>EE9JB)lvjw}_i4{QBy@ zu7?2l4129b^KW$ir?q$>$!^Orh3Efuy$zVIkb?z49Q|im(f21POd3~MWIy?@>;IL# z{lx=KKr1X$FI67=S>V6d?SQjg^yEPO^Zk!?At?p=5WkH_d;dSLr*Z&++3_U>{CVzA z+WS!xwDQv#4f`)t3{dg8 z=P#c7opPI%`(1SEt*LoO(EVz8xIVzPTYI3jJ6qT7Ef=d}FPR7szJp1Eo09dkP-rNp z=3M_Qf`p^x1|e8HxTET~h{}DfusPCoyg9_-znF*- z1UZaV$H!+Bno}rh*&V@Pn7wQIIWbuPU3UgJfcno|iIK}3wIcn|3wEU5-G-al6Y(Ow zI?J4l*sjM7nA4&3B@%5nh}<6cIrO=}Qvb@Y!n{?OTnUm*M(qW(qqP(ty*9O*k0X}{ zeKx4{-v!#^z;q}Xh4<9{>X-50hUxs&BdgiA+J(zHI7OcWCMC+$dk&P_JrD zsS4_J9gL0Ky!&Ousf7%tLor-+mnc@t{FzQ^AD{NIK^_(dh%Ms)O7D%0t|9%@HbyBz zXej`Cz1ucufl^U12Vo1KRpog_ z^^<-9V09mZ_yT#Bhgz7nCC6ZA$)q$*8Wx7GoHgJup=M|s* zvcwdCYw32=<-4m(R@FhDXsuoQuo9yeDnD$Y*tWG0->97k3xH<}U z^I>J2+`e|fZYKn#BZ1YSDDp$a9UBlf{czr&>9B}@1^cQtH3 ziJSrmhZPkTVxYDmH%)J4ndwfto{&?SO$TiVhl~VuW>nk8YR>B<*{Wi~gw1@II?-)4 zUOPXA6{->f7LQy(UCZG>z6Q^(qP&AO^CeRC58Eu2TVresPvm`S7-f-?R(rHPHTEN% za1(a|;o+_2HHs7PeCVit56ieRrbqb&yzapv_DiUJ?6(ItlwmA`6*wBxdr-*SiN%jm z`%2IXx<+N#Kp(Z8*u%i1TKAupD1d$o^(InnjpZo8PFnK*D8B$h65Fj3Dk22>ZOH47ZMsM@4K;{Gu|%^nAP;BiGwUo zV*TYZ>Vp_i8T5$H7q=6Yf`$8m{$Nz|4I%zZ^XML)gP%QP80NQyVp*i$q>LVfVX57E zDGFq_auYTH*4}v`0C)0a&c{ZYp*)(5e4>BEZ(*$Xr3+nGbbBfW@c>m_4U!(f^I z_;b)>JUO%VG{+RE7}dTJovZh<+8Qse(qXI00`=$}m=I8a`0IS$3Y210?ft-q)l zGgbz~Q7p8K{{Ix02MB+Ri*6n{PHa9l2T<6bXbGs23^b%E(#H6o?u-MgvkfhcK`UpV zN^RtIaoo02^8YdT#@`Dq+ec&im6fyZ-Gz0>TG~dDqWRjD);&sBvH4Vfg%mY0V*eTR zZ3}>X_OMO{AG&XhJ~u9RMqRm!dapy6EzslRy+pf=QB17 zUb1PI$=nOGyGYG366kp-421Ajt*XV|zBkidYM;4X!Twob8n$O{%7~z*2joWUc(Vv9 zcOgLnbhia_N901od{YFlm(|uqC+|YPFz8;xa_hPazvN*A+uz;Yz{pMlZ`nIfBj3tX z73}CdIFXXuwehWhhUim;SqspRzfc`K?M`0`Z*OLp}bZ_A5|#BmDuC3qXgdxu6x*Hc6AaHch}ctI-8kTxgr406Z)i zB^$o;y9vQynDOqR)ll@qR1gl->@z^*qRZxW?s!{%26d@QzyPTe)wI} zBtCU~dlH~v8Q zCnUodjGEB(%qE5PF3LkH4bgnIo{JQffA@qZ+yjii-~^uF+qq3Kh22^ic-^%RRdBc3 zV=nO)RtRn36JVUc_|js|pHzo1=Y7WZD-2=oyknkpQ8mJR{a*~j7ZgjHU8u6tbPZ)AW&NK#LxV+e1k!3KnoR+ZBn>`tUK7trLK&X9}`YCcnbb ze(4S%IyiDh=yL$ydIa#Ta0brX=~DV$UtLUr7i@;IqsaTj1~~cu%I6bY0JnHs9R4{{TubQvdk4 z^49DKK*J4l$;$m&&{ye_+`o+x@X%;L|JiEr1mk+VzpCA;HdG(MDt2IjJDJsF>QCWy zA0lx7^VVB|XFxlIjPN`N1jjV4qyN;E?lxj3*p}TU?7-bjFb)4@4Ba*v4>oqUipw(X zyMGol?s}sZF=#CZu| zBkLn65S`QonGxUW#vlKXD=`0_Ec|f7xg z@5XD20oLeUqw<|GzKm;x^2_#@JFlfdURR)Aq?dZDuHfNR5wK#If_6`Ddm9dHyx489 zNf!$%0gg`_cs#55Mqf%p@jvzX|AME7KQ;ngw7fb$Dh2q|==t%s#q0L)a=<1T{w`AT z2YV`e2*$E`NWlQ@vOgMFA=wEl6{yLDfoBl{_Bb*;6%YDlGNw^kp4S+xLVaa_LHVc_ zrEgF`p>E3kNwnKp{kPQx84{Vc=U$r?;HOo9fPK;*`LV`ScCLTtU7D{2UBeiUxC*GN zGsT1}?d7ttE}r}w2$H|dO1P7m?++lwm$CdAw^DoyT-?(0Kf?SEegZH)fco=@QuEe# z3TT79UasmgqygO)6Xa0cXi&JbiyeGm&c_$;@op`>8xV2q?kl`IWqJv;J@1i5$*mjn zg)%v$-n7O183`0&NVh`XyccMYrEt6LOku${oOO|eTemO)UQ4J$%;5(j-GI-O zO*Dz3l0WX*nyssW5^6>gr&+fF&s$bqQ4#uf0W^%R_LA-fCTb8_CYfvGM+C7AP@*V? z@?oWnIgKE?IiDLZXkaOu!Z+i7vSW1*@gXZRx6%^m=x@d@Fa?5_@+&fM&Z3M0aPzi+ zuCQx1lwARSg)u;@LP6=f9pJ>tG?eK4m*0;z$NK88jt5R3IeXg`>T*O<|ahc<*HsSUdSHe+%=oe*+;8Tg!tNpDR|p zX(2^P9*5EXTx};#`O&-Aq}RRzsF?jXOW*oYNb^b^_BKCVVCr9iF;q^!A`|=Y9n5dUQN}^NjA1R^jXY*{ zN|O`QtBe0yl8A1lrusQjvy!Mm@uqZVdbGu-BdHpyZ4FoYr8ObFfZnz~s$IGh9qM$= z3QV31&qJSY(VHQnL;OVJY%N z=*{#xk8gGjCCyNl2!PtyCK5AjBJJ%ysmqdnbx%gf+ENgAxV{N^bQfdP<%*=_MV_RW@ey{f2swNGs@k@B=)Dys&sPk}Rw#mNDG#n`Vu0mRQPM%YwROVlCN9>mPeO^HBlaDU0oXzu44j2dg6yes9g@yw`NsX?up9jO|KWvmyVnt=8&!!pDJKFxzE&B^Slv-E<2+@XyPyrP+ftKifPmqMFkpKZqHc&9{K|+sj>U~R0fbyF04ka* zmF08y@m=H>)a+DOjA~i9StO@v7nTi5RWzf}ovz!zr1>lZjRm3w<_juk_ahl`H4I=I zjG9amt<;)zk09`Z>cqV<(SE6U{&*+1QGkFdvJP-}P+sUMPvwLOK5n!Lu5;UmcA>xx|MlxO%Rzg6inMqd}nWC`D3hs|5hO zeEAeUHupo*C^}a)Og~0C-hpBwr0Q@P4!B#jWsnh2gHSjdw0IH z3*IPlNZY1IQ4t1*VY!>2T5ZQ}^1kmX35npz`0I#?`}mgg`W!TGqej!arG)y8GpM*m z?+K1YB8%S*BLG<@iDQ6O+_(_3y8noYN;Ux)lZER{GYnH4I4vsWG_xpKwp*SumM=Se zXqON^hNI?sz#-Q^h?-i_jexIj0obgW#MHciM#N^^g$1u0di=7-PvFI)Q$TOsf|9-U zulM7;zwCnIePqrCn9B&ETZ~};%ECPAt?e!2@;#aL=<8Dig@Z)>Ai2mDTZrY6UX~X| z2cIuA4%?|a(ZlDkO@-DrkNbSCUns|j^&97M??VIZmdG$BcVP#>ZyX0u70B<|61VG4 zW-CDT-Kc9z&qj$khuyC0oxmpMDU>$3mhE4;@S>L^9sLt2ZWiE&Ci)XW(+hz*WQqP6 zu+u64%svUw-|cs4!!-}vx`g|&(}%_#0DibH&u|50Cj5Z$CKFK9lV^a| zfc1(63ULE#V6I`mmoLwIq*C*{8Fd>Jx+`&KS_08ZLAfQ1;Ub%ZrdNnW8y*_*OaUgF zOf1(`xak?3Xk8N2huQ2BIQ)z(Kv4#a{Zs~|b%YAN`Ay^EAMD^5f^Oy{Kny|NrFvg5 zE-1ZN|9bc4x(~{#gI0KCq7IiN8H1n)OnUe(w)R>W8uZ@>>o5ZT&ALTgfH5Vj!_U{^2T zm+xN#qH$+9|F!FsSyuAaem7H}N=CWZ7yr!FbDD~G)53tay(%+OtV;*fR-tdR;6py2 znV~rafiqy(Z4RW12b$|#8Auu_|>|m0IhIpIGOMUm)Z?0cTryehXyAV^OFzL?Kn57c**o(s_&3qxASNYLs&( z`+XdgP42Qym!OE6Oy4Txu^l#6;r)6 z76HwZ#SubN6GEx+NS*+WXEW_5A;!D}#LwS5u~4Tddk{UWxkhj^sBuA#6b+Ipb(9P|{dB=rK#<)c)7W9eb(ruY7|1*RO&qq^@> zC#%R=qW4l*Vn;}Ze0(`JcOD@`B(hY zM?*liBuntD0k7BlzdBDC$4xv7B3P`eV9_(qjl+hso|iFa_64Ukn6(^wUIO+q)@Vsf zdq7JB5kRd&%zxdNh(z5bh9FrFfbBvI39c9b$sTQ2-WT?a=bV66={1v#@IpPN_;tG& zs6x4iPkxHRoK)Rf{XBb`%bpirEAp)eCnm%#`}&35Vk`Ca zP3`2ilOLl!lTjMr;xpMhtdmwU>K-l;-*DJ>LwieH;lK7Jy3u+{Ch$huk98N@j8d`5=ppOvI19U@TsvOjA>le$x~( zs_{CkgTZV5z#%qoeLX$m_}=Fbmi!i3Gnr9vyrrYZ>KAi|$9^Xj8T$!E`v%DR*OjIQ zUD1emINX63-PhWo&xv%{`x!G8!`IdQnF+&G84e`i-B)s)V+S3YO%`I!4C)wHtLzQI zs{kPE&^#i;-Dc5pTZy^aEevJWDYfr>0=3`I;qY#@K-DlZv6(()(+05WygNCIGPkX?UzUx;=NrymWIYQPBO-a%RxC7h8z7y5(Qe2Ip=&cw@uC$WK?nP9U1It+24uu0hG`*5a2OU z;m~5!rb;d&PZ*Jf3bLnQ4)QVVM=$6~FXljjKi6hWEho#1C5x zJ*-BG82}|2GIsufnr~9I8IXD@_!?7J#rdPH(!N!@Z7Jf6k-kFl+e190##?*T3o}Wac`T1 z5M7m!`SoXiJq-Q*EV=5-7%eLZ!`0QUPsB87b)YGQMK;D02T%8fCMf-__G1jPDMF1YcD$xY z3{}USWrYxH!|-uHqab7eoD0scUtz^x`p5YvciAp9U9|_S?_d~xgXQpyOny$IQuHkq z8YH8N!H*;|iG~fnWF&VFx52RA2!e9HYILALLXA4kqG}!=?XX<9aOw#1uy#M8&*dIO z!oK8i$$nx({>k<`fn((LN>iZd;nRaM-dqyL7BXn1P!qiN#T5YhS4l+}PD-y90mHP; zN0=aY5hwb?baMq~)7N17wpnr*7`$lc{{>JSu=+8;W1~0*BYJZUAeT>Qq;$&D`eT~E zDO^ZBa?hqWYV&ad`xa9qoy0!FlzxyRFv+ zRDMIK_;u5fHAi;sgaAhKB6^{P;w$sGe>h52O_M?koUTcSLfAu28TJ!UMC!Er!y)O&l8E z^yX@q%}5z5QQy4slanbQZh{_%nW0-E<@!DY;yh9W4n^LsN7+YPWTJISsd0d>GNWye z-70!Os_%8A%5=EYCkN#OmhV^un~K84Tx>#5E?gZI`Gl33%!3nd==?UyqlTF9i|`vX z>xX_@0C_n%2!WDC;dN`t#z8?6PfWmf7AwBiWp{P}ijRMHSFk;Rg7L+D(4(%qQcvkc zN*;8IbBV~QsIqd5olAsNJP`@niJH$gt_J50Yr7-i6JupQVxUMm^pdYG9`p>~XK2lX z^`P)0UDid3vbCD5_?Q}nz1MgT{SXx)3!=^l5UWfJ9d)4(S$OoLN zk;e42r&75#dv^mE)&oJ?>Ta0UAdt;ykbCUtbCjpQ5)4sbFE<8YSb?j79&Hpy%Cfgo zzodXag5OXq$;a>gJeo4>0hlar{WrOvc4pss*%T;h}IXV4iYG8t{?qBRJ~`oe{2t3bIrN#ImR`v>m1t9 z2a#;o5MxU+$rqvyLdNw`1w#zY!{RO~M3@G^0pRmc+W;$t{hb+`NuXS!x*q#| z)3r9)Sxktz(G`pq zbqpH1T~IZg4`~p94&W(Re`f|QbQ0GkL3Sh0sQN~&rov14*5wab9x?S znaCu#LtunMp=FltWIvYNy$i~-ki2pQv$}~>@IKO9t?P}`3mxboMAr4-F~5uOI;`qz zagjD-yj%=GEE~p$fzO!Yum*01NIg)dlSss9p4}-7#ZQh~G|vk=^cNb=P10#Nr#`TH|RkP5D@cu(N|iC&sp2 zkf-~TSE`nmZ=Z}k)POBmz$N{+_m4K6d^&A!iJDh#&?r!8FL@etkQkB3G=H=5%NlAN zjjO@vt~=0gZGW67@TKZeV!PKGtD+;QCg{tYBFU;d3=pZ^FX@PfE27IgwVKniqHk9b zsp4`dzeJi=9v?W;@pHfMXaR~6>8{rE=^4FBxA`MsoOr-CRUAn)8!W!`o(&{p0yxFV zt!BcoYq$K78Gc;&NVaF+{gE3)0L2dKy}?qW-HIh#R;A^LoA0Ft;+j={>&9gs} zzHR)S4^bOpxsU2tr83es%aaSHaAXxkp_|2sNfxkNd_IiRnm#SSKNc<*WM1{zuKX7I z9qeuW0Ex7%Igq%I%(8mwE0}t_+cOF3`^@{YSP$1l8s_#tNmkQ%6gcZMvve+O9i&ksg=0)#&{{($v^i%Dqg@u8=9 z>-~73FODgk@|@QpQR!}8B3YXfmdojE5KV`HM@%6dURK%p8glN=eG7tKJH;f|Qjp=G z`weX*W+s{k3T}yQ!$gHO##hNdbyh)Q>O8>X=t;z@$>UGFSE<`ud{`Nw>8b;uGLE_*!q+L)^3oIMQNrd;Vi}) z%ahDeXZID%zTxzX9skU|qEwI%~RX}7uIt?o?ml!M(o={oo{tB_B$>>DdlnSwl4~djtUGKKCm+&#ee_}U>o*b zf1b(~uC%U!5}khf6qiGGn}xQhN_?0Fr&^O?i{*qk)H&$#{Q8m~=muVYQ&9sh>Rl*r-aUH5&cXyW^Omr4j7hJt1Rb-FMTj1V-j@E*8Jf-$ap}Y!TBW^KH zfEPa9yUTcBmOyS~4|r^zSPCGLr>kioww8bSUKTku)c47Gf2%P=8Hvk{#DpFUl4r^J zr(XeMMp*ITfFVVL6vQErpD0u#*UhM-Q?--?K2kyq5BWn*d!a-f)V`S|R z@{n8BxfRQ+IvpFfkFU2ZSMn2QC8V`d<;HYUoEVlEapQo%@}9kr)%TliX@H<5zEWSDHZw_^6ZB_!FAId?aa@Cr&Z$028|emsDgQ*V>ss`0&?1v~MZs zE@l;nuy;LMbe3~qyfowlkOM)aod3E{W``2fgx3QU+>d_-UEMI@UbiTjcb3pmpPC2R zoxZb?ulw13j&hH+6lf447c!-A4gXeyB$iELhqaxS;ax4`oVsQO0vgd_P8v>%wt9Hd zV1+5MWTAz(iFrUhka??olnb5`^JKj!lNO9O6-RiLJ!aUcT=cur9Ic5E~NSeEo|`;-G*0g(?H$@IJvG92aJjLdIAvi-P)yF(Vg%W>G(%j zQjNF_J?`Q7$x45U9&6h!ZxQHu-ybgF%w;oSp zG~z^s4GvW!>9{ae?2lA(>vMl9FI2SMS(YEx{w1}4IrEHJ=!(j5x8jzFU!U z`OQ|_8K~y_7*EfiihC#+H=ex|-3HvH|MG$%Y1=YaJGks^JC$b%>^*X8;qNE|SUlNj zhAob=b@viNANks4UdlqMEuS*!ocHf>4nPqVKG+<((FU^*_rsv72i{S z3COEY@k;kZP$?J5rOUZ1e3FDIwt|8k_#0ScUdS^{W!L}5G)UyVd|AYc2s*vJ=fDhQ z)U=Cd&308x$CUR{#e^`A){bvO8qy}W@Um9%AM1Co--Aqryrda)%x;4^DZu*B5&d^^=_9JvkZhCqvqh|+ zTzo@d>fi(w=WjF*SR%%+BjzloUp^i6*j zu1?>OfdbM}{0N#;eLWA#VhYbXIDgnCm6D1}W&sSgx*Ja|!ni&PDS7)Lqum8WM$T#R z>L2FxO*fbB^_SH`iTav~UErPeRXa+c6+WGA^|evOD)(di_|g_ISdTxqTkDFGtRGtUW!VrqH= z9@QD4rk{9F@1T_{H~5b&m3+owfJ*-|-9uHhYGDb3(OuL75aYK+F;H%C7cGC6ie{ML z$b@pTokK%wVXFJykPnuG+?2ZS85jA&el`p!1nS@=L$O?>`vnG4_8h4Yo>@*Pl9n z=Ni~~iWYto0#G~01h~tLeiUleTHr*xOhd7E;M+LIiMyO}(M+tcFYng6;GrI!eiJga z>3`WIZ>VydtMwc`y$H8Yzl)8jV<XYu~U8-%(#>iim)ut@~Q5$!D!@s z1bWQRDf;=Bazc~5)q%S|MFj1~3CqK5C==!GR{b~K$?@r8>x?lO^_}8Vvsa3xhoyWa znJD5o25B6DMD0@f!6GtwyD~4&u%+`7d}XI90_bI&Kd~h&GfUg5(~5mwUsmns`Aq@L z%CbSWtL8~}ww6~}_4F%uta7K*S{P2MwBPzJ5tv=W9p9wzRJdtAjv z(6KV1M46y~U%LOLDHzRrmyKxE%OrZji%i&*(23v4#%E1QlPX;z?VbodTnly(56Ze% zk&}KzgNGCrfEM<<&dkPnFc_Adw47bu%R!4L^M`zsU#uBA-nUydDs?)#=#HG&$$$3p z@gc+lO7L(?0#iCS-VP4+6yYPSUaUJwHVkmB5Exf@PUjZU+a|v@&IyB2`Y|2j&kitD z!y{#9TM(EE&Y^fq?`{H~R3*y^hecy>#YDG&<9-^V8KwzERL*Z^K*Wfr@~! z-iZNr+dI(HL-m!JMG7oaOEO zbe1=35yAsMd5RC4zt%z>Qc`t2e= zpYGnlO-;VpG-~~@rtFC3(eQ{xRnKzG-qHuJ%-sbUBLW*=tduK2 z#!9&lFp!(SYihc@8b)pMYTlTuUUi3C8lqS|x%1iCewC^%m$iGMsgBgxpSUfO2I;SW zg2pl7f?L*e1AgO$9NC!}*(S(Ss(Z0P(AR19b2)Iz&Elv-s{}9xu z>dddthSE%ySmpMBrwD?}vb{Z8pf}WU2X(>uHF#{8gK1P7XGt(8Z8ENdb;mcx@}}r- z`(^l^R#`dn?|xVblnL5*%me$&Gr<@=~#%wzg!6Z`P|=`rXB@H&VwaFgk})&Xi~n(J0S-4BDyD@E&kz zQuo6CSu&bX%bU!&N*Yb^_ZNAJcFklvhuvG#W-xajfefZPoL=_D&7HT}!@6_k;j*}& z=4@`$-uZnodv3VYW&*QxZZN$cf#*79Y`XXpEi?LU_F__sF($3y2R%@mp04Hkgz-l6 z{mok z#|Rq9XLRCJEI<1_AERJbe$(g!m*4P#}g|Mx&!yoQi5t z`ovxv%51%!`em{^U3`Xg`~w^-+5uEOJeSe6rF1}EzUNd+d38kAP>&R}$gX@|ulpB| zzfUdC!a90edT@(3Lnn}*-$#O>!ajKB+IW(Or-7u=Wr0q#Kj@y4WVv%=t;8ZSeLGj3 zW$z?o``I$GUi))@bMw%au2H~WuJX^#f7PDy1UXHlTbj&A_=s_r2HSriUR&v9!&P=0 zNnjeKtX4oY(kbl}(sh~GPcYvvT?nVwIl*upf_Wp;NGsJ$r0=#kt zqt_)&pq2^xyKk3zk3sf-^8<^azDwuWiz}M4WY=O#jJIU!JVB^bs!_i|tCZi60f{2} z*BiI=QnwP5ccpf~MgGhqj6=h)69Y zGlYgOblQO;y*4?0RH&HaNwMzwWZ~p2$k;-gg*=&%g7&IJ_%}R!7#QCXx9db9BdVp# zl%yduB&i~3^&DdV_JzhVvdR$6<)XGf?ndP&!rL8xnE=KtJP7-(w{O91rIwigbQ2t_ z>m*k^rrqcWbwdRuhr|r|U)Z3n?jPO$vuP*;qBs$0-1+)kWZyE<5{YbK$R1+32ZB0< zi3~~(beQ7JftMi)FbC6Z=WU=P@$DJGAwmupSz`PZ&4ZRgJL{j`vO?eLmO>M2$5S$) z8cwk2;6}2vE!2+}6SkYGE|$Xdx{sAn;pMdwTr6A#N1%tQ3uTD*%qIf*KbCxI*|}T9 zq4dG=!}3Sad>nb%vs5uEOl$N!$QeA^6QuB`^aN%N8CEv|NzEY`4O zFTJ@fB+6iDu)?-(4~z#NYGfW6{P4!~e@QXsPS4()Znn)L$W~9eXy(>=TbR{X+DosBl$6zL9dk82$ZT$>=g-Cj=u-V%GJSkT))WDM`%t-=MgRf2Bb%>Q z9vz0GDy-yZa4empM_a!CntK`9#GDlLPJXE} zGXKk}K1zR8i!HkN9T`0)s~p2*6E!yVE9cUhaoB zh5mq{_=p&4IQmZE(<=eEyNZUZ>AC`&v2>m(mh)*&VJCHUPs?Kk8}%9W55J*DNg)d? z|Ko+SEs)p{THP;SgM{+YmjqQcq(oikg)e{@h5A;@Jp1>%sokkxqG4ZvZ{jsn_5VAq zERPEOPAgx43A_XN`7k|!>!xD~;&2dd=)`{AN9=>b(DJYI3y9020jtT)Uy%1e0deJl zzkvRt?!-=PQ$zg zMf11*YjAPMprM4s?*K=J=|5mg>VEPDCLHOAK{Z||FzY)HD3ot{zFp(2(mB9CW%N|b(RUtFV0@l22}t{12m9~ZC-f=0E%$dIk#cvfT(`OR>FB1N^n?+3&5vyqe4=ifpoEre zI;4z+8Z&ldCt$E-=Q%f#>Ie7QqytQxmsd;m1|RMYDvrqw9Qz1eSf!w&##fjc_D~6S z&;h9^ivOuGd-z`uNO2Q=Z8`0wKK0_g;LfR@f&d5H=g;Z#?I14<2X$izpP6fS`JdKK z$w?1LqX2t4;czB@JE_;rdc3lnWXQ?Tw1A!=nz{}wi|H`p1#e>3)HAK5liv8=y>FJZ z93$y&>=f=a94xxT3qMYWZ8%^bQv3%TgYYSz=D|Cz8q+mpZ7MtyK^E9^1^$7%ivS)D zG%yf<_u*0k z>i7R@zb;fyWQp4_;p~hHqd7_6fL0jt7o?ofz>kHN-67igZ<4In4`l+_ln4lLHu;03 zCmi@AAa}^2@!ozyslD=r`M!I))2w8&qE$ada2OO_m)Ft}@QOWmmU+?qR*N}#0F{8F zQjrU(0hG05ln3O91L;iIyyF&;|zZKkmsN zV`U&kf&XU$!XLEnEnh>aGk(5_Kh4Jmp!un*NeeZ(dzZxqo0BJU>)BGx9KYxcD2TQL z46^nR1ff&-Kj*(pd_Y}116xATAowx{DE>`+f57w|!%_6O6sYOt6RUzM*f$;`9o;*6R%wWG~|P5?$v@dNDoI$0;{+7vCaGrxjv)ydRq6-tDWx~ z`&zIJ`1N3>29If^Sg`P9uCpwi`vjbf6T8-l3Lc1F_^54^ceU6{!2{~9OKtk}tJ82bnFn#iF2=5KX?t~T}2+Z)*u_irNHt?}!~ub~@B$M1q5 zoV=vf8};j#*t*Oqu#pYKw-{@f0g!{-7wXnW53<~i|Z#ZiOP zv7?n!eC_5$cWu2P{F!n?-Cp?gdr)O)iI11~gZ>)uD%sweD=-+bVurYL;(WUXXDFUb z>Sub6hksq~&o12xGXZN6UZ2$)2JYh7x6Mcr*AbpICYKTfd1y&>py{RkFyF*ei<6M6 zeDlir9C0-E%7jlryCjk?$$~5(3mD=A82zq(i4GP51sz(gA>2rGN9@y7T>T6^4H5wl zAh6fSH{OPGls%O}TB`F_4*V2X#=Enpd$Y;fk!*yx@WVXJu(wpYkq%$ zDbDE2YLw6Gy_U?{GjrmncV+%u=7Om^;mM3t2m)|qlp~nO&G9_g;+=KqpN{`%owQwjNoQ6(AYpyT&5fayfy9uD=Lh!9un>I6 zru~Hbq&+Q@q30X8v8QFI0wyP>%c6cZo&f3xtBHZhO#NT(pv5R)V^%D!w-TVA$U29w8%7aS zr>pi$Eem;bQ|HL^U?@Z;$dWtZ+BA12oW7SBHnl3IK_ICkl0MuCSce@|CcQJl2AJtw zHA?roU2-hnnCW5}($Un@;16L z3!<+p?J%{(Acw{6r_+;eddw;?W3Y`sPTiXLaWu;f2rtx1w zxN~Gz*BZ&;Dc%@={fJwDOSslz{O`l4#Vk|+w{U#nqOv4WxvLT8_$s}Ghd!nmyuyv( zlM_dwXX#dL1w%Sk02Fx=bBg^b&NepARmG!Lh3yZyLi{YUj^@F|F)(`Dx%J>#DJEmK z0A!SBmg`>Hle4k8n8(4drP>y(Bx0Bm>%qde{tW!yml?&$FXs6r2>!FktN8j7mf|&M z_RgCqZG#yW>#VO|mB((luC>YGU-~>T=dq~0>7uwQF^r|YW1mmXA(2%!y%FilQX$&k z*Z~E!s;W=u9e0?~Axe5PgHvkvqE-Tn^XKk)zf#CSd2_g~lv!zG$!E`y;)--6X6L=q zdp>Og<5DEgYp=SR?1+7fB}{*GyR1x9k63Qs-p>;;w7i~A=J;-x ztTka$%BI^|>vCE3T^s)wHKP^Ts)pb`-yL_&{jCnT)t1U=L7Z4@1OHBWlq+8L~ydhE7+u?*@-B}BYV->X4mH~=l&qc?! zbWuk`=Jc#-Wbzs9G1VQc7NQ9@6R-Q6PSuqSnsDw z372kadiX>ZJc*xU=RZn>`ArU>Ynk-QhXe!|=_$-9eo#~MyIJJG{mlbuS=~1bwm{Hj% zWb0?j<9N8hyai+wn9&SNMP-d5INw?6bhbLF>t6Ko4+LyVA##P7j#yhFEkfHJqiZ$D zmJfvC68Rct4w{Rxr_5SVp>J6J5`c>C2vM^~mDpKfk+_A@(_XwEfxshCd7$ng4oj<@&VCy9umS z?Cv>tv*b*?Dr*4}e6MvD`yvbWPOXdhx}1j_#d^FRiz}vwq*DUA0Kvzd>9A%5Ey_5XbA$-s?|ndW*E9zAFUr*n(KZz?ua&cZ9JP(OgmdVAu z??*y7@QgUu0ch1oI?IGbb$ zEylR*d*85*pS@J@A#A;Vsq?(F>aiLuJ{5G5W0!NC7y3tqvu%Yh($j~qJSqwo_59^s zG8!J)#a9f*%<$1~OvH8q$U7Tj*DK2X`uMN3q`QX@p9GqkW9i>fX28G4fjcU(}NA=J;gVqBgvq>D-AUQyC)VH@t`$5AV&u z-L{8Z+p!g1@8x)tj(I+vbrC0Ew4v$moXL3Y!+Ub^r*ha?)JPU2y;!u#Po{f5yBM!%vPMG!llN`cbD;-+vL3dKB^3}T zH*0y6O>WGXFG!Ii4bHb1427PG0<+?h-8_8j{^Ym<(JvLc72|lrrv%IYyCpbN*yIG- zcdpU|8w%8=87|MDIC{1f%M^7Pkn%R1{7O~<+SlRTa6EpE^8n`CCoJ!`*E|e#$|?C7 z?3Z-qrHz%2ZX#U{k|m9HryHFrk$efxc0ygjw;e6**WOLGyf*vE@+Xfv>vybnCa!)Y zFT^9y|jJ-wvYj1El z94k@1Go`5_$=6OtCph`_1Nd2^g;QrzgcezOiCpCZ0Wa#*ck~7py84ziwO>yyXh2Ku z`!3^;lp*Dq{&L%AAOvpf;=BN5=6CA=jLB5VksCK#N0@Hx6 zhkoaE3mRIgnBe+jzfdY;M4SI>!^MFywC4;vO>B$CnZINke;D-xUDCnV!GE{Zdj(UVDQeOj?h$ zX4^DfHeA}99zB3w905eUzlld5`A53<98 zKr{BxgqzD#S|lT=MBm5y26BOW?<6GfOm*O54zvts@Mk@O(TFqpXAhpqPipzKpQ$!z z0NX~rlW$x=#%7Z3h9NO7x_^74N2YD-JG^uw&lYZ*Jg)p&aX_Fy-`atrft?+;I=`dg zyT@*Nx3J!c)~EujEgXi`BFp|E%kmSXRGJCCoxg$s3Wl)Zp1<6!1df32VZH?&Kif*ds$-da;veC4(S_yy>1Hx zIWsJ%A@!L59)PZ<9`i*?7-K$K0fcM!K&MF(gRTq+m!|(zzK=VmA>D<>{~bVDY9|HIMj^OiT<+Vz$0Bcx3>s>8b}^-`yX%va zrxRIY(SuSYz^QO&zR@szeZA>xp}yez zvxipClg>^-gM}pzyI(g?T6^o|8=WaqJ45PeyH526Qn-;NT&{}Q9d+fc#HeGJ$?};I z#0*o&DPJe4%NKo12>duO0P`F!XEo;EdqsWWkq?${HBE5Lj z*42f^EoRT3L3VCw@&lgmMD-9r$VZMVH<>tkol^Qeo23e{Q z*(ttnM~{h9Pbawy8}ek$O0!6U>OxIgXuP(61#$#6tI}{uR@B>`xDGvH;%p#;vr{1i3tV`#EN$h+3 zalWQeY+)y6>legLiOacIc3l1f_gfU}YWiMI4SwFh>uhUi4YJGSmOsFYc)gtbHHcDv zt^vJZG9V82e_?1TZWmJ_f>o6ic*Zs7d<6Nm-QLey?zh0SLsd^h)(Diwp^@K`-FqKl=_^Wc)R6=WlI00Mcn?q} zafBO2WR)b?U?2GkT%t#3K`Ff9;o0VN4ZxDg<(Jg`mSi_}p4QpE?{x^jT$7wBVG$1W zl~ep7i?W~VpElVsSc%09vW&5R7*B!z`966xifg3Xf-{frptL3uTDvZN)hUom*U@10 zre6X#c|d5FZBEAs8nX*V7gR@HrC0>JfXGP%*+EXTK}mqIzuEa{KU|-(iW0Gj;_9_O zLvpK78T6l|%5pJT)F;8X2DCT4k$I1e4^UY$=jxW%4$}@A2Ck7PYN$;KnL^PmYX$|| zNc;+j42YRs43O?xK5C2IlSIqFas{i1YwrozA#o8h@d1%fL{05k+@i#nzys)$qQm2q z1&oDApj>(h_t3_XZU;>kpM!`}(?TN@#nHk1(U;%>LyJZI-NN5cDKt)*g;fX(KEg8G1#xPr_kiU(@HQSK4u<2i4W_@}Sv9_U zYiU!ISZeA;Q-OfP@lGQSyK>-4Yx=nZ-$$7IYjjD$+FCWazWk7kh5_(Wt0Cc#%OMp+Bm`i*C zezhoY6uU$qZ0YhQ|A~KG>HcfPenPf8wJ6E1k}btH2+aO0&msd8qB`_{HFiLRZu^5< z(2!8AM9p$zu#==>iIx_g+K+t+F4D+&Dfio-`n5tQA6+Ct+(%;q>KOyfz_mp8V~Z5vbUz3sxJ71_ptwi@VnuPdFP-Y(kME`%7uzb* zvc{%&03~ZZjY(Kr%xep!9PLf!g>h4;wO!%=T}MIzP*fCr*8U?F9^lIz0Vs^k@R!|v z405|IZs%!hFd<(#;nKT)^MeaW203n2fKw*`;}mQD6ulR10Ow~?F6!I^47M98#~O%D z_E-fx;43LcG}p`j zGdWcZjqK``j zI>lRM=)tvt_9WzClzcb#7FA@NvPQoUoD~YBuv{;6hG^=11czuYV?wqor+(|R*i}nW zpHG4T6*CvhF^JYXaNy6W3P8i*w&G&_O4R-goP`xF+ZjKtYiJ!6~(uyn*PO?KU zq=cOyS23oO_LsY_jN3+clst%eo_QshH2fc63wk5^_YNDOJ4iI&zvp%?oycN7tYwED z&Yx5K;)+Z)i>FEdR(}a8k%L`)2?2t9lM$fbBpeCTb3+rmOOq^*gqCB)d|>KRQ}Q$N zo?|yFV9DTLLK$_Qa8@G_Imf`rbqN`Hi%zhB*YR$YY6Q$SX33vR(7;*s_}^K@jPXy% zx-$^TTKTTgZetjXDwL&?qP2k*v4qkA4v6FKG?a|L#hJcWVwQ=jUXfX}K0cP3=QpPt z%MdlLD`8kUgOsWt4j|SZ3}T{BSl2(85LM`59{C-?FE|xSU8@uHm+NgXCgrqvBbZc6 zbgS=$?l^FWYYA4j#s3sw5V8iT@z3}YgX2g;G}23-Oh%u;l!OghU5VL%2f^}9vHN(MK#f=}~fvCa;)7sw%3XKtH1bVRwz+F-S z3L8INJB2kkOB#4JD)znSrLUN}KkJ6$$W8d~ViWxqQwy{dO@4 zqwWD-Gzf19%)v4p*g1})1@Fxm@nL{{NNnsS+r^xtEEoF!9#c?EFr$xWFeJZj2PYO7 zD{XrL$j(n-vu{@{=Ry!cAOYiG^r=$K8nLlB?5vYuy@&p+FukV-Fdq^ds=Cw!%-ki? z9q&y2+7;+(6HXAmi-1eMdbX&@cio)ahN7r`Y=AdJ_?0R^3jE65(US^MmzPrbbPX7FifA0}LJ@S`?QILt~mrRae%~-5eu&xDC0T4uAzRT8YgG z2xo!`JpTjBq(R&rt^q%l_oIL5tn1(9vEq_@0w@cg4pVM|S)j;K%xIJ^|G)9rC8Ml2+GXGmQ-<&Pj&s|E_?s4EIG) z!tb~IMbho=-Hf1Y^*7AiHzUtlUZ zBbiI7G~<4%%s&bM%pg`u>qhIQ`Wh?2oze6Fus)BSvbDVm;TLT4Sj7O(FR%%8@v*>9 zFZAzg@-IpC2+)mvFMW`jKys+IQbtl&9ZZFsW|mo@yW#|xk<6XN)}=DR?N2I;#YDTh zdCeX@ne|ZF^@dh^;ZkWFcv1qu={Ouh*c@1CFpiIdm?9b{QRy)S+no8aQe4V70-9j4 zu4ha!Q@(!xwNmg9MH%4Jmr&GM%l1u^-a~CM_Uj+-bgpM@CA08QIGdl#6tyXTSKBeR z-56$_7&V4Nx6xA9kka&NZOMjUMH08pmM(+ZZ@Lh=7m}qixV~>2$`OZLS$=1<&Q}$( z2L&4{g}>b`R&J6WA`y#L7t(nAN6VQQ6ni&c@pdxXs_!qE0z>hbWx)F@>`~d@~ z?@g_=qLs&!a@GP8YHn={*w%5cO&v-qFZ~-I8|hH+*t&O=$YPwy^)g_-lF<(!WUY+Z zq_a;(2c7p!Ia)$ioCaBx zG0u#dONsCOGH@cTTq<+QJ0dKhu=*mC> z0JRl#tXHbB-4CvWqE?g!mAt@Y`rx|a!#-AHiCTx@sOzX3xK;yhg9UHLj@}i z>2e1O62StK@TX?OYg4XSPE+9vF6c7g|J~6MEA(5-#o<1UHLEK400AE*sp}DRkk&6s z?b%*tKRPx}Vk)dIodrrYdw z2R%wFr>T$`+qNb?U>8n*QS;&7%Om~x7b;8UEzlHq_Um<;-Zc(PKLCr3Fe#_3#dHB| ze~&E6>b4BTCWKr!U6CZ6{h(d|{*6)BqbQaKn%mu>ixw2KNumfxL%GZPZ|1McOD%fh z_h9qyuf}P66N(QMKLC5{@i)WvSWPId?!6o_4hB6BH^al8Lu`&nwuP`QLj z*KQ^R9b6Yu*DVsqQckWlyYDQ6;fm`R_C$A$Hd#}>fh0Bce)*j{xGGb)Tpaf zA;V1K_bXJy4zcv86@S8utv&|ylKt`_3npl%zll)@(gBAfK#kc!sI5jUToBX&wU>sp z4$tGoR0qh}r;;&IFjMS{Mc^kWeHv+`=AjdFTrN_kVxO`cd3?nj4**R4e*vJyniVUr z5#TlD2BwH=qGL1Z(8Piar2PA3b#%NMx}4%g#~hErJ3{aIMyZS8)R(RRPwm||2KFL31*mIrhSP@tS)?>&|RNJSzw(VMnVsxwEF|3Z>&O=;RpW z*W#71z`4gZ5y1*&-w8NjK|i7I_;3_L4fDI~Ez`zMBy-Jdlwj9SvC@&4*jn3#UbcG1 z{c}npC-E*Hlpoh>C#>l6YF`E4AL2g%^FwaD8?@%<8M+o?L{-VC2rwO}ALzKi_$c8>>)6snFSx30_HB;~kL7e^KKP|_M}^#@H9+_I!m?h6lClQaINVHE^53au_V zkG)Q(xW6927P?dgyY{cqYQ$9RTzN$?GG(DVA=B4Xzf0PIO_|m&I6$Btd~pl0WJKWk z1no|^Kpxx075Wd7mg?C7l(q#NBd1MZ%@CsS~ z#q;YJ)JM_-%9qifaCF>2*AjFu0caEd3HHCS`MpOt zp$xv&&u-m63$d|t;?MAlTQ@c1D<< zKOYI_EnS>%)$2XJXC6smW0{o*t`buW*L5}pYtNmGfamk`@HM{5WJ{qANT1()zDez)b z9m;X6!5NuVbqc!ag5CDLp$g~Vr^}YxAT6frHLccs(LlO%yuk|g|7l6-mcmOL$u6*W zPXztEt^epl`x<|-w4Ja{bF7SmNll|5_mZ7vNDKY4VYFd=tk!0gk1{-U60=fGI6`;j z;7*rv&%<(ziPag&)dKm$^&+XljD7sze}rKHS@gnbl=Qz|#%S`1ib&~?7biZ?%^zIV zsxg=;48tv`{N|ZgNR24(cD3&NN`Kh)%KWNP9hBZ_ahrOy)V?_6^747@G{~=I zquENAcrNXcV-k2`!X#=IE4mE*HVUm%Lg=QUfcS{B8G99Q|8~>5p!{uxn4Y1{%S+yVA^7Q(2vzQ`;xafLnxb)J}jQk*BJO5$VHNyM>h$0-$cqU&F zEMU9x&%B-{x@M;+C>wvkIhO0+i2i7E;p&8a<@Xi%&Z;RzfxrYRVU+S#E8Rwk=;o%MF78=Bax{-rr+E9Y#^HQts+YgyL;yu|)sJe*PSZ3|N1x<=w z7Y%Tkd`f>4eZ1Q4BemRP08F*cEIoGjODb)hCV9-mGBq4QL?v0R!D~EDUcjz|Q!}uc z01t!2b+mfvH32ZnBB9SVCN1rbL{?AcT4M$TKELT=Pv=Cm`YknBZ&0^e- zKGp1UGT2!dGg;R+#6ZjnT}@y0$~6@XGw`{mOlyf{))ud~(yz+vtma0Ku>R8$fP`wf z`G+!@+GD`e(*qH*w^$84vo^c{;c`qxq?freKD!DbsJ470CurLCe~nM)X=7X1WU zGIG}1-r-(JyfQM|59fWcbc9#e$MTN*y-}jRw_8I2lP(@mE$lqdyT*=6t0z_?*+h)A zYRxgc<4?6b-p)`hCKW!qtXRkw@t%yXt|$5VKB*nr+Zds4HBQ!ED|#>-RbiTZx@#?hYzZ7H0kBenKaJaeRl0y291Zm@#T(~E3o5f88=zPE#%exVL@Ht}^1F@I z1%b!-MzEK^^iqt<82dOqJVzqB|7UP|x-D@CPf#CcUcyacG!i%7i>mvBGy_5p+s2sw zFs=kfjAzi7rb2fi&Y$n5{Z(0}V7)w*gPkDC{A&}otsRyr#xwn9Miaq}nRDg@Iy;7j z0Hjp#m=*xFQYV$te*-1U9G#)#4&cKepXC9qsRcOZM@eL-Z-$}bC zcqVa*Po995WAXhBp;nuev1V)=H?vOeNx5E&#!zwZXtY^!ig_LYA<^P!p8XFYxD*Ne z9u~Izdhv6Zu6US?uE&!Bw3+$99jnS7j^c+L*qa74i#VYW{Bcm7m^t`w--SDEW&f_ySbhC^6}7FG?`hh9SvL`11Y#`OVzUsFgu3g$84yuUgePga)aao<-BeMY7ZsFlSUm#N|(`hSCMsT^{v5oY(Gq z7%zNmgw=6>I(Zmi9XSgEfhBT-g{6{l!h=t)9_>eoIRJ_h=hk}ARWGt|k}7()+wd|4 zQ4yTpt%U?Q3?$EMoK@`Gg8v$l`ewa#TZdhF?{#aXeWGq-eP>$o44sN_XJwwK2+DSH z>H3=}0XW$ZN-UbrU`A zJ;Z|Py;`j?feINI%JK)YT55}*{wR93R37$QZx6i7B43>V3G6<+WeY%~K z9yy)Fzgbz@U~%l@{N9Pj;}Dh793LH4>4&;VM^(=+;~e&o#8YcFIH^*f!GT<)NLp3+ z@au48YuBn0g%9cPt098k=sjvTG_M9pj>Tl{f8;M1s1+i@*BAgZy&tiYMP{rm{%Ei6 zA!l(-p3Cg{BgR%r#Q;g(fgEpk$ID2N5n!OAJPb&lQ(narM=95@tGQM$W_q`EaP4H| z$5-Ge8b%wqC_7E@bSVhxlCUtNipQi`5oC)5bnY)$b+hQr2seYBVnw6u;e;Sf;>@^6p--X<;%)p^&J-JoI6`daFY~&X))%2Gcd6(qquu zyiD>sh;UR{LA>EYp=VC%5FKwD5lM?-qGcMp>uwl->@CCi8({t>gwEwY$hXROwl`k~ zL@GuO;n&lo{$eP<&(*qk0NT)!`ifA-&IPMi;%Ys9d@2rxe$J@x$=QLsL-Ik>(14`k zW)Y@pZ@m_s+eShI)1>5F4Y1=Ij=R;;pjV+3ih0VcoN{OYS~RGavrdZUGEuF>k#9a{`1Lto;|^v{~_ImA>GDeWbk8>lcZvVvKlX8agdYTaV#8Hd8@Q zBoPRw`yFk+0q%3=2B#nym|r}LpnFC9<{E$5g9B}*peV!4CW~Eg5RW>!M&W>W{+`KM zTY&vT*GgMm4n_lOD$A65Wj-GIJI*!&W@8ryjVkWYCUgff#LvFKh>qlH92tSQ^10c( z9*7r+W|8ROr5+m<7WJ#7i$lR=Na*AnNIiV)4{dy>8xvge-KN-5& zck+0mK4!w+i;owfwPAw%IkLkCXA^mOIJ`o5{FmJ`R8jz1N(3s9oYbTAM#v_W_L3FI zd=*`Ss+T5MXtsY%57K|=0ZY&bi1sO@rRWZI40j-P`+Rn)uQamL*-XhlpF2_uCp^y6 z#%YET%S)=0TwkItvl(b2ibq=Y#g1*yEvxz=gCuoub3O3sPexx|#}juk5z;LxzgK3O zK=RH3RULtl8`1EMgbUBi0b|H7T-lDPb4lubKsh#~Mit@4 zJ7wPXr5|Y)K{oZ^GpmE37iH>-D_&U!W?$uW-Ebv@FjcCJCAl|l&q|7v;8i{T5x^9M znXwk-&3im4{_b}J_zcW8e=d;6S6u$$(Tk~NzY-d)M9|~47v6J|4%>|G*a4NCNfETg zXdvl1QV-q-T;wR2`dtV1{41IBhomBr^qyc<+RRcJiz+_dOu30?- zt!WN?mi7^mrDq49Ueaj@gyvHxl%g1OkxL#_1Kr7LF}YMGG*cIu3eV+;LG9WHNJF{T zs3#O0-nfn_9%+zp52;|M@}%1P`caJl=zk(N75n8G#>r+vm!8;m3&Q|4Xs1&+^@?K# z;J!pSL-kO;_V=LXvr$MY$b*Vyxytui=7ERFu{??{T@Q(K!iUkaOQ{7tEqloEjE6~; z0h?hX(hC8(aL3Y>_Seq>NvZ(9QS9+_RXu7UcNrv)lD9H6B{&7QEp|zea^@Yzjei0p z?|6fQaFWl~$4#_O0@zD}_gpja9xv|DXIVjvV`M1ixS((&_m-NB^%JAz2=xtgo8^OF zfnPfc{f3^eHz3hV3Z$z5FZs!|2`3Nym<}Q@+_9sV3iSe;DuGlE20b}#8Swt(w5Z24 z`2&^-w6-;Q)}=}cr<->HAX901GFU9>`!X%{m;G$S8+Je9%B&^m#tOVRp7fJFa6$ld z6FSy1PQ4EG?I?7-^ELgWZ0#=v@A)&QPb|Q0-hrH~wHRt2j)x`pQWr+R3PzyV@gmF+ z4A+Rrc}QGVYEl$6vx?%1ISS40>0XlzR<$ha@Qs1V?7LVeA+L8H`o9uUfzVz!aFPL^ z{wl#NQyKl!aBTKiW~JCHk)i5M)FbbJz&fIEUD1_;1yIG>)|i@axfCDfdnLd-BJD`v z8jbDWt_)W1^-I0=ZFn>)@IYVm`;Hms``zXW#m&nT&tt+{ zY#*@v0!rRWeqA8)sZwaBKxt>?*a|Z>6Bbdzy*dSKEZG^S_z57UWBJ&Q3en_sL{w$? zB(Pao@1BXb`L47CsG(?+zRORZ>MemfhGMtWCujRADjaKU3X*UMS}CbNW=sCtjYc{uqr)bwvzAdmUpH>S;wEG0Dh!Ya56~L_aYHDh(<;T|B zVE{UACOpS-bZ2l-y5}=&e_{r5vWmdlcpnvU^hX+UK3utKQeD#}-0sIa`e=B?s3qO$ zxx;vG)greBq&()*=%3{5YbfMK-_`bHa#%xb?lM!RJD@t10Hs%+R$Anm93 zw`IyNwJ*1lk3YG-|MH1HpqAF{V|_y|8Gvj^4lqX}aD{=XS<9deG=v=H==jT(l@TbCnR33lkFDw5xb~U-#dke>Zz6=PYU4LY5;U{-a zUTHY)vGbzyYXXgA=pGBcSw9QL^C(ZjWnIX48%HztO$>wyGHP6+vI- z2i`{W=rXAD>jvz=42%?f&(iHMl0TwF*=o$2@c7a^}>)P{?-2(nfU z0vouoU2O)ln;~LYjds&bLy~UjoBF(@FkPpC3J*7S;nF13|EZ(Hgk4_=u3r!wNoTxo zM^I30)|Mj-3>#SxSM?2-t>q%)Y&Cz=l1CEC>v=!0g-ChF|&v!crQ(?+L6yY+q7qIBE7hXLW zsLpUepUee;<$ULgn>Vqg{~RJ1p02HCbV2ZaP*K}?>Jg2M?MK8nyi+0)SujTYC*uzg z!zqFcRY;#^_mi{M3htvu^Q|MsQP;8Ybp=Z^*EL*Bm%x3FN9Rfub)qsPa(bweJc8Ns ztV#@y%H*u0Uy&pn4_{d=10VPI@$xILB6WpV--@AkE-Z;Uib&|x}ar6g- z20xt5%htmvbIgbjV(<_9#<=>+{NQFQJrMwFz|C%g4zL9-$eyluYXp)~7n_q*@{9Ro ztnPSD^~U7b-_!>)9T&zyR~r^K<%2I5+GWyZytTKYp6{7uuYy^q$JrODqM0*&R9E8t?j|mhu16b9WvJ6VmHRJjdH zMe@*Bd!()eK9UFzxUFgEQ$U*3syZ(NbuVi_prxZwYNOyj)~v{~>HJlKygi%rFU!sQxgYA4q0IVo8|bt*JCj!^sHg;{b{d z6%fVgOXbbAT2!r=rhct)WpSnnl-XTOR40faQN&EtN2>>(-9Mk>w!1WwdYtHvdsxQL zjV`+RbZPPd`I-NkO!$>#D7oAPgfVjGw$cZcfk_+99owsWWdAfNNfUWGf<%92I&2Lw z#3NU3%G-aJ4cCI5Du!gk|BtXc^R_=W?9f=!iPGw z?4VS46pA0c%&Q>(UwF`b1u$W6 zE(z2T%60iZmY(uw1+}tuWMHXAON#s=h?@*00?KGf?Y5O)eaH@G7@&Q#@@G+;aoY~o zGtO(BIR;L?fKW}W`c}3u+K;`c!fEPPh6lDc(22Z#TFeVu@?iDi7`eh%ov1OGPDRE* zUF?&W(I*u$SM8_`yy~5@wxXK}Nj$&;d}P3Onq8}zE+6qaI8Nd!JEh|t- z?wQBW);6}sgm{jIaR0}J!4Wh`2t@AjTwofO^I=7#KJ~!^s<5mFOXA! z2pv3442KK>872u8}t#JmJG|FJuP2TBQgR}>NS0}znxF6(y8 zp0R?THNY!}KjfxdB8j5NpApX+SPP=_My2Nl8Uf}c167Nt0(8*Pa?KwM*_+7z^OH91 zIgGOz9M{7Xkpp-(Dv(1aK=}R-3J7}a0JvaAiA_GfdV z=dVC&HxIsM7Q~X2n1tEtvwpCs=%(^teq5xyU#Q7qA-+D$@DENTAUDXzg&4FDYP#Fy zW}D9~w;BM`5>bz~-n)_E!kqVz%!^%6ISXf#GE2Wd=VnI5cdmexcg66UXp3#7h>6Rb zCw?8inG=TVLAS%c*c1Lo1fD)GF zNGJGo0oeOmO!iP_;c&ZF9l0qXrnDgayQ|i&_b2=LD?0W}%+b_FP!J3#)wrbwE0~qfbb&C+W6Wr6J)(wmzmas0t3;E)Zv^pv>SAO>n5a=ctio>n7 zyK1a%OA0de1ZAE%?QW0L9jZr!>vG=+_>sHMMqoBS6&Zd;YDGG!XK+YR%4RR&bA+8U z1TeimiI+_$8EfigwRUX3@^hSPfWL3Up&Fz!iB!V!K1dhm^Asg8Hd=6=jL1(D9YG1 z`I=I+4Rw079V+*+fiBlWIM3awMh=0)nUz~qF4zyQ>`J^e;L%KVBrZ95n)?YZ?!NLHAmBR->;Z41#1mITeeztpXrJYm@2*__vZ*OD=YV=*H9%s- z6GN`1D%NLO@t9d9M2F5483~L7g>`}xOJDpno7`L4haHL3RC4$VPc>yxN@%1M`ir|( zI1UG{+uUz?W8&z~^T8=+0$NVqk{ONKKpgL|K-J523kJ`PL_}!qr8k~W1+*i2JExjc zSC(eCk81~|hH}JBJW`068vZCAb@lh=R`#?rzRLIHtlLYRAzLyE{?|h;7J^NQ7&%zN zYF*KoC3V6(LT%aBbVi4*-ggRjT77tsp*(?_kZR7hFE9%=Nv{)9+xtz-TEO1vh89x+ zZ+@$obwYA4YO&h+wb_DN zab*1q+;$&*%5K1x-#y!^*#spNNolmmF%6FQ9U`e0N}+}J+)y+onH-9Dd`klipSLpP zaqB*#3_c@gqpjZ8qX*>((HEPD3B0yllkGA!GDF16y;%R}64x(2(_++N&I*!Nk zC&*Mz|P1spsnY7I_EJ$AxdBcGK-C z1NEPIySOA&842I?O1)(I9DKAN@e=4KJ2>fG1wY898Z5c`dNs1)3Ia2FD_sMjNPh(| zU?C;JEkE!#xpx!0#b0j?fpZ=u9j^uI^_?*;SsHvQYe{4X+&zt^*i7pC6r S-t(FP{`9p?wDL9V{QnCwT3NpU literal 0 HcmV?d00001 diff --git a/rfcs/images/15_clustering/perf-4-workers.png b/rfcs/images/15_clustering/perf-4-workers.png new file mode 100644 index 0000000000000000000000000000000000000000..114a3d952838897148baed70131ead9e2976597a GIT binary patch literal 446234 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

r|Ou?t!dVNvY@osZ_Gh+SXjrkat0Y<=5iG9B9 z^~iMHKD+Jyt?BiMIdLKNHazqE-Wg)-Ea)w=?1BIV%N_P~Gq=J*K;T(hPD27l`1t2Gx8BA5BV?n#*q^u`dNd2qnd4jAQt05>%m?kS{)@I*v z#9-f}MzXlzIz2~AFEkSHeow;oiS4Xgf`vUayZ z$9bPVc+;r4Uon z7&y3&ULBeP!CM zb!u)XKmj!9Z0w!sY1U;H237D8FfvEM zw2s8rds{AJ^O_BG+76)Vt>O<1!?LAtkXElhe@YS9`^_RGYkgUS>G5uIKJZDjK$ZHA zjs)-_yw3^a;A0mM-NHpxXg1VPNg%6892a;I@(jd{jHjHjo?w-B@w&CpCEYgMl?V$o z%f7z(!_WVj1yGRjgJK#Z^;697q8whyUnt+2%+>0$C&ooD-mW4_7wKkKq~0Q3>Y%Fa z1hl~_!@CJ3QU0dldBAZmDUY+lgF_$sE2uq)Wqj=1>0mrxTfU4;Uadw=y~J*SBWgL? zrx2K{-+exn5#vjoJxV*l41UUw}$oOCymw0sxch)k@%fOnXUrKo5HqjI$aN)D`^ z4g>^_M7IVYwmrD5_Fao3?Xb9WzA2fyDZ~PT;3l+9_$~wjUEO}_W?N+je1;rzhRbw| zLYBwe-+Np8N)w5uR5NKk$&-dWrQ|T_6d2X~1$a02ALhVK@OvYB3S3xa;L_92ImYrO z8N!Q#>3f+am%79a1#H9`zun0#+#giAmJazmZ>4xO@F*|qgs@|6c{^K8Q8yY{U|lv{ zr8%7u$-Yl-;>GooS>JQA@Jkb2#Ss~KK<`LDX-QB>*ne-69%W}~D92R4Z8ZajSGP_c zGG{vr5@?&Pk;6UiXfOXDRqGwJxpSuBfgJ)y1-|NK1u%&}xv~5;eRtBtAB7)(v3+L$ zFhSA_6#M|Mh8MYM0no1gbq{FH5ouG^wuFyCw^Df>t>@I{6r5$iS(mFjBw(Lv9O`# z8I!{utdepzU&D7}=-FSc7=HJRp{%q?-s-+l*3kA%aTY|>8z}4PCN|%l)96$G!nC{? zk6_8X@FCifTSe{mxHC7=owr^MqNJzx^iWL$qQQ~0l~fKE3C=D!NZyN9Q_q+2YLJlF ziM)R<33>ePqQl9+^Y4DQKloXtG=(<=GMX`5RjbrP^T2(n8TSNfLT8kTjGC|9$2w+i z!OmPKc8RV`6U7vQxSlPmEyx@sG^|{Q#SMT|bY7Mj>pC3Qsc632-pm9;(wtV#7!&l> zpvs{Ej*dgX9xKh0j|v+5UoT^C;t_dlF|12ErRW@%KhRul3UW>m1EfyyId>M=c}d&hRJ`0cD+rKFy-s^P z4T1+o@#pdI@zQ?R3+BT}!--a}gih^O#Ur=dGI{0t|CE-gW}{eJkQ?P&UNjQM!Zs4= z#Cfw-z(|Q+!QpJgL=@4Ik@sX&;VE>q z7W{_J6K#4zH`#eEFxZk9l_WYqzeOO8p)Uhf9XiO_2kV1m~aM03=nGMYf?+(L2PH7RLf41*KjK|BHHWx1*9p#g1zsLTB4{|()6)t@C->oRk_SjpKf4<|9 zZ}-eo7JT|5S6Ds!O@c+Ge;f`wA`pGl`U`;+4!~C>f{z?yiwPU9GN?T1jI~0K2+Arv zII3cD5}FoMiRn%nOTehm?I@-Fv&76Tt?Zh~eG6u*O0-p*rwYd!F`fy8N3=Oo16Izr zB?jk;m*q@6co8$qCiQ@kCf9wZJ_(zlXySRG0E4SgQck%VJmb zi@^mj{=L6B>FmYFDR<#LBxnUa8_EeKv7XAVAGz3!T*$G>asPNwl%lbssKQSeJDbiw z?>!OK2T+854XE~W8rw2FM1~$GTmHbP6;#hfxq#84<-%bzxIDHb3Q&-!kj;Z4p^MY| znN{g=$No+lW+JS${nN%r{jcHLi%UT5-OVrfsF{}L6t8MJG@1!)x8SS*nqX$^o*WgT zytAT={iB1o>?LnP6j;L1XW&v-Ug0nvnrgBs3{*JkcTv#9Oh3eAN-?`s4;_g6!^K3? zs(Dhm#H;OSi!ltN8tGGI@H5mDW|q_C!r!&O!tJwOLaQwP$3I{S1lZ0~vx{S2r)hve z6e{^2fJPJLvPmY{X660*TC80y~s>`UpmEF|7el)x{w) zvGoPrm)G%m^-tWE1z}olR_vqPQ16mhQ%b@{jK|&p$v0nSR=lPy*{Q4iq252^rX8S^ z2n7R!D7h|w2-z?U#~4?ot*$S7DCg5xNngBc&GPz^ymd}^qKs;+F6DU}6~lw6az9%Z znvxF0!BL!8=a7Q`4G9qW#QVwt$ID$F&t(;(@>w4`|LfkAH4O7bKI>X}dG?KH{%~IP z`y#t2%SdLnlGMZbLG^D0`D*N8G=5p_Ta1#%0E^S1^p0&^B(K%St zO3VrU^lwL=eyi0=F0r2_&5AsG(_S06vc)4Oou+#~}(njlbhTfs3 zX9S8`@S^hf)m?znqS@>DarXH=!<+X%2fzVi@PGzfvBf3FberB4<2?qS7CT9aVVLmm zgdUnaOinXTw~uZHUqo)1OiN;1S$Hfs3v_mT$o4P+mA1w1J*V@z*#WZU)ro^&K*11)h3t%&#Bc<^^exc5j zpQ_vKVjQ|FUB%b=`ExiS#2{*~t)Qe`DsgCzS>RuFLssqK2*i5dyGEMgEs8F~F z-CDUo1FaX(P4WGfoD~vz?w1g%&sx-!O~FpU$IGw4Lu3gmqyRzy@7QIFUnQ*o_R0tj z^Z#M*t-|W~w>CjExCIODZV4LPAvlEK8VCe;cXvy0x8UyX8wl=h!Cf})Op*UNU-#GD z^URrxxtpu~6bV%AUoCmpyG-651`DZe+4WriiTkiQhq5zX&5^ zkS3?dJ5Ry^XvD5GjoNpP&Zq#IRCDxp|2C;c?R+PsMXDZBg)-cCl`>&hIxWARM>f9?zGW+;r(iNM&qj+JK z#-sL8r3vD^WqGgRN+$DFn&AtWV$}B+f!x|l29ElqPul>%Sz%|{cbfl&S3>_Mc=hWm zTKYXE;c8d+EjJxMGEZgDSb8)w)PJB%fXQpfHGv1OFXP82-0(8?uj+IRF8s0RU{2 z3_jjpGQX)&X8QyIhs%x3+Q4fdpKxFpcs}sW8p!Hpk#k|zWK{*xfwm?$u{I1o>HTSygR$TD#M zwiu~?UE562MZ;LNj|icCLk{>W9%0Ct{?vw8J3mcF@ATw)rfOtBmlW>QU0K22=s?LF z3EgGs4B@bEA#qQZ=%A}{N)<3uTv0!g`rF~bwwdZFCA;Zis|IZU2p%xIAs69TKry~! zraV)G%lUX>hwrNO$9sXNBH4I6%9KxX6R6AI?hrokg}_mc_Ml|f4DU1me6B@OK$%Ev z<$f`b69?n2wTR`IqSE)d7Q<{{Ne9$8rCNpgPWH}=B3*^c3|!33zJ-#!c@e~!v(r#b zF31;u-_yt5BAB{vTgFsEQ+{f-?QYpE4B%knY|VjTg9JX@;pLqpt!4V`zIi|{LYgrC zD_|y%GJ8hJI$ODeQ!C6rShpK@);%W+&YfL2S_0W*xih1Yh_+UFAon`0sX6$so>Lg} zn~58t2BqjiQq|@tvg~;7fY-$HC5*Ku z(s!}RQRlua?+KO340T|uNr^Vy0Nn5cFwX#OTaV?VWhryd=`Cj7$K-1w&L@&(t`Mv{ zp!c=%YhUu`WujnK@t!aru2;GutjK0u=C91Y|KDWRldQ6KYie}X;on37R#p9YK9o5P za!|V=j&y2o4%8cyb#a2~1lg4zL4WEKt>eRZDJtV0CNgew9C$)^}v&Unt5b$=EIRypi%nraT^Xf9-*i+)PBq9Y_ELa^;&7DAbjQJgx5!HRO0 z?CORr6s$&*ZeaAMAn>|O%KA(kUEwkX_1u2 zj0}W}Yg?1PloZiE)_(Qt6A`scZur}yz1t2I@3}S>1kCuL+_3XOeT)1*$~K8Pf`Lw2 znRtmG>TWy$VQ@~0U(zGxC$uioM`ej*Jry5=;RlI8=Jx>m~ ziT}Pu{lSHv(A+E`)?&2{yzKT<&pft}|!^3(ea&#qfuw9=+GH5{! z0V4o`xaqewnWqd4Ofb+6^K!Vac)MHh`Mwg*ySS9_$JkJ*DV|Fs3pCStYgw_@KP?Eq zq-$`%+d$+wmP_M8aa6ll5ho!q1}&=erx2Z7xtTGfzIIo%^@pB&wPs%19>l|9XiwfZ zAk;KjQtNB$dgbp&h4J#O?;>2a;-retK0M|_u+J=g(L<^07lw|WuU<}3ZdZdOvyiQpjtl3-yTa5lysxjP zGQ+}jj&zj7#FQ|Ed!ba1NLAcu$YP+RKAjG{j93_3J6oSRv8Xw*+$5ka?DIZ6t&Wb4 zrru;sKv)KL{#;RPh-DUzB}FU35xcUAGoNU4sxzBNJ1*UL>$>D=zh3cepH3hwM3d zSus7Q!j5ursXVw} zfOga|u>@3mcliCpzEEN_qhTkc-5%Y>R`DRN4dVj-ic^m~{&-S8yCI#>I?`Fe%iNVc zHR47xf>8ZPMuf{t(I*aE%2igQtV`Lu>MD!9HoaVpv35Id9Q~x1hw0G58J%M3L|G0& zi?9X>ENv^-htGwpZ89@KG#ES9zaC7T!SwDp{D*Zfx(emTr@N8K9>>lz$TVf&+5`^x zMcZ$a=2AZ5&Kd7y7$`2KlYD%`(t-*5{b;4&Rl~YmidiusEf2n0ZU-}6- zO7yX^j=Px_ejqp41}l^|z|?}s2+eIh(G#1e(Un`$4nxn+`T;1gt+2(k+)nlK8?lzN zn#%USHsYSYu+^w(PrQu&+D7F z+NY`#&2G-9!kgF~lt`1+8~q|Yms|b?dqv>k&EuPwT>rKuTQ*D@$jXHNU zxc_1gZS`G%tqz<1_^}?ZM({*;NlNS=uAPt~?<^r6IjvzMkCAB#TWA+ND-T&vGsBkM z;1%Z&lW*b@aZ|i-e$L)!CC)62kw}aMmNxOnX;EYN_Q`dIqyG7`U1NnvEk_1T3UKI<2yCsi^tw>*(1@<)!1 z`U)hrL4onqCCG%nVb7zMrDv}Ax5Ic(mt{I0#4mRw6cfwWb8T+$*q@Bz{Cv>mUNQaL z=LBixb@BP*?JUce9r`(bF8mSSo$k&7>s;=s&294??+citCt_R&{*O>#VS-P(Ix9S> z>Q|{}$iI+33ai-+Jj%|l=iJrbl=}mX#4E=4y5Ntvgki%6D6 z_%w)Xj{y(5=wru*)4t#_V~d(x(HRYBztiI{EfK~}Mn?-4sFyxzv-@8CX&SM4kn6<{ zm=;b>;7tNlUDmUxjByQpYsqX%pbT1Yx4xQ=-R2n-GN|wxKkP05_khujstL;(R#ABc z+OdnBtm3Em5N@cjGYI-iHeGuxvKUcWGH1jt{ zrPxsPyXSCnZqT3KWKePyF6(2b|3vN~ak`E9l5z(KQ)?}t=9>(RpUcOf;HIZ#`i0BOvui(}tNHbZKnBu=C;Bh4`tL0GgAJ z{_UI9kC<=mBtC-sIh%2wuj{AY*mL$1zTbzYQ4>5O@a~sv5K5$A4wQ%EZU2Cn*cy8B zO;<=x`?SNBQ1HzhnfLh#nwQQ>Xg}0YeGvYj(M|2Zm5{!T3*abk43Bf0cb(;8G12Z+gC8mW{!H$4* z;p|be?@o3-ckshya`xVP-UYBJdfsaah4mz6d}dGm^+4fZ*k;Mh7#!G`1Nyk5ye4hfiYGh0aT8D!=(X&E`vBZAsdi3rrL(&<7$xx}7|} ztW!U_lA8_LTe$H!a35*6E0H(bD_Dg!wo{^cJ0B|Aa3?aP-Ii=h@TIgdBQTDTi*Lh2 z0+#Z9s1M+@fo)p@TG}?^cm}F$`g~jTQ&mCTD@ny~E z5*{N2OT9x;gU6}2+|wJ6`IeXcbB~LAvm(U|PFo*X$xuCcXWVP0{D1UuP6qaa8tWGS zjBL0|66<+_b7@v1lxjCG`rIrfPmV5I5sX6xI|D84jcY>%!6n%>9q5&Z7kd>CD0NQI z0~NrS)m~50JMAx_yi*@f-51R1O9Y0iC&OMMZCVE6=tABbp>j5$Y<2am@5!4slDU1H zE0sO4|8;OaOpb1!t|(4pFe0llF1 z&9k;abzlIQXJOooT!nD0{du>iTDmb3-^uzAVLm}QwPN;e++i|EZB%?JU2A&(oy^e+ z$(xmG#dzKNXWsey(XwK8BhWx1-TmP0?ul>^(ygXk!0$#ZqZ(kyHR=^rlVa1c^Zd&g zAB~)%%2yVO_8rSPI!NZ+;3Z?V}?=zh9OQxqsa>&kWZSQ&wAuf~6#6Nj;#?y1jA!I+n9P z*%*o>K5Stl=D=r#9BNk^Z0Kz7$-&G%(^X8@&ok*LLgeZ()10!6p129Q-MXknO)-#<}a|9KTWvDh$*>g@!NHq0TPGOR!b)c%9G2xcv?03LWgd z-V{tC#4rYMysnULOqan617~H(8f&)-#u=WSIn{O zD`^C90RfF~p2n|2!oRPN8sj~FmXtm)#55I~Lf29vWZ(qdn%cKo<3owd6s9_B>=1vy zK$!$Qt~F-|_h{b2SbL%%B0vRlAL!r3qY;OBotY7trYIVmLeg$MO2;=p_kK`JZ_$a* zk9Z&1b6YArZZu70LM>gUQN-BaEizSYeELSiP)VzAsjsS?ec+^3p}I&P!)dVucNB;_TrDT=pSGqIr19#uZh| zpEVkJxVW#~g}YS87AlY!T~I?OF}022KhKvZ`qj4EnvpQAPsTrt)*QGR@*`2799{oa z7+;u-m55|eCKL_gJqjd4rewzo76&5*`X?`b4E1VMK!7Nkdj}I92A5kbT)?c{tJ`PF zSvS2b0~gxdq8POIPei}dxgrZLKR!_JIZck(F)vj%oO{)AQr2`k%(I!RSXy~(6S5;P zS?u?o z0iq`ADB5zalDtN_EYLOsIT)rSG3_Ez7 z-i!fFW!6LDQ<9NEtBI}T{VTAI5)Um6u2hvrdz&S^k8!S`)VC;OJ+C@Z&;?3g$>8c@ z2l{I}YY2R)zy+P5eqN%TgS!*fT;eE0@Gw916krpc%02LT&;%7u7%*?kJpRxUM@vfw zU_?LqQ|tF^d#L$ZvQ_v_a&+V>44AyT+v-@hD5Y(AzMJ}DWy~7IPr@Z9NL4MYP)PUP zKnOoLb^=NYHN{gw#h5!VS!3yba>b;&o^ZCqCRDkd3jwhAmZ|@%TL;XS6se_Zgi9SB z8Fhn{Tq15fnL~TL758#yvlF?6mZO;1_ zSS4TD9~O{Zw1)}rtmeOwaC{8*Q5nm7IiY3Ost=RoUzfVu$((?ht!XspM{n zd?6EF{f_VcqP@@(!B^<~u&DEM3C6;tBFfMUL$U*r!^J~P;TiVA7kh|$*$!m0@bm1s zKxNY7@haBd;ovR6?tyQ!w2asL_9(Vmp$Tvd`GaS-#~<2#=gU2}AN@%5?95STnKvsj zqR&zAfZ@ZY5a>pjPylxNL-&s`^NUXtZ-=V6pZzgq3E@(}sF8FnabaVq{6>rY1ZLjy zx{hGhr^tUsRg&7b(_Lt`)<0}%7_wFK&3<{nF_?Pzy&&5Yn<^fsJPn!cTPV*Fg%Bye z`7ljsWWwe8_w%|EBJOk7(oj;5lvR>VH&KcrA~>As@F{4izkLjhkY#&-gvDTo+<#^`(Mv?#dhfs_{NE^a^{J6E*>NS9RmHJ) z32=>LFba<);e);2BYrxW0;9PLhSb8dMb06Ng$p$}v;6L4g!i|fLn2No3y{iWxrJ%; z|B6<6`5`s%CU9}2DgTPr)4xPZuwHeRQnyU3<7tk1bul_Uo}+9-PC>uPesrYLdcgp- z{|~QVV8GE=dXsCf7s4&?i+Xyw79`9DjqWfw)WDq-O%-k{=tZ9v}n*zszVG-R=j zNvJVw=?X^{Fv-D3Cnm*A!S_G4v04+L?sr!~R?4 zB=fiY7OoG}zxlrMZKd(5$a7)pKQD20xI(kw6BEG(^Ix&tbHXPU=fD=ARHQ-sTir=p zh24R)U0*QlmQ{q@qN#Ixe-PHr#~l>EQ~18~{ZPhP)Lw_X)Vu4$sgu^gw|tCf>QuM7!3gBY&{Q~xbwE@TQ!0<~|g#)pj=6>mw53eC0d9mTLfL4B7q6J!C&ES5`z2 zXB7X_Ak^@sjK?hL9_ztdtY4_FzOv-vCGcrzM`&af#U#fmR=J(!EnqotJqtJ^Fi5>3 zKOhFZ{YT^7jy;xRs)S+jv@E1fywlQDFAue1cwb!iT)T8)Y?mGF+#FZwwe_3SqiTc> zVkpK5XO%v-KSdsw_l@8=@^t`IJiZ>4ODGpY@bz;YU`C%%{&80Ap@Q~MG8+ny275;e zFr^;EPp*2?^#MYF2@uoAo8y2ETD~a>1JZ=LJ}Y`12y}2L{{B|C(?5ct`j=sEfIYm~ z2dx~6b>^vQ_GfaZ>yK9^+^gyu$VyT=+l@BSq%@82m9`(5?I9evEeI*Ed6vcL=$eX} zv94b(5T@WJJaruNOWb4VouXaMPy*4iODs@w;NEG?}9;?W|+jzMnI-y-})Nwk=Dt9+b9b)#!u{M(ry zL{v@?m%lV%6<^escxY2!pG4!wBdJ*a#=!C^zg>Ip#l$@#NBYWQ0#OVli36Y5ro>)h z0K3ZmY(7sR!@>w`{KHE!5X6}H7Ky3!Erq2@91)!>;+-$Oo}NyP@MtW}Bd);>sdFl%BjSH!YD&>Maw8b}Wkq#c-Jk{cK* zvau048%Isf)MBN?$1f$W*WDyP|C!M3mtCe`9lnelbcu!lAMyt|nRNXHAVK02)>CBG zUG}uUA_DxaQ&0dX$4e@by)_6YDWdF%A_nU3`rWU}BWwJ%=>De;tqV|Gnuk~2znp!} z1j5&N`b}%{Q8CJOF4vnh_-;N3+WqTs?T2yGKNyPD=eVlV+rpbAHRIvF;;l`O10Rjl zuUf03V3ydv7wpQROFcqH2($d2%wzj*!`uK3Ys!-ck??YZu8hQy&DZ|cx>=Cu-$DLhI?>-C zpC%?|)cEy`Bk_7Yf_>u0a!*hEH0=Tbb2Wph-h7bA+Lp~oqN>2JksRRH6`Or(WQHVd zp_7%DjYDn_`^8A-F89dmo@4x}*8Pi-#Cz`~3<`6<#W)PGp;W$j2Ae;g*aQ zf~vh=zWho-GIOr4(X@awB4uX~M+4ma|Au8S>yt2Xh>P5pBI6%yg!f!bEX|y@f&;r^ zUc3-&@t_G!d9oAk>D8}(f2c;2;pB|+Fo;~dvC(R~Vo8h2Z*n@Q&5=jk2FJT7zu@pi zY5?Nv^@hRO$z>I043sG(!jp;zFen_xSQkXESg^eX$!q`{usjX?P%_k|%eRXkbuf$D zn458%i9O>>v^MvL{U(DF$7a)=&!72f+}zhgR{&kdHuhf{Jhi@f^UcY$1lG|&_|1F;umKdJgik(?v&-%dZytk zQc_+Kh48YUZqNbAuyCj$xiIaQHvq}l;VfQ;1QVj?D`t~f_G<0#ZbCQz{L#WGl+(|y z$gsQo&}4M~&BW4b0+#2HeL2wU;20eTBX6|78IJP_hIe;)vlcxTNZ`bH_Ia9RQ>CP> zJYMG!AG4zM{yp#Ef1fw6v#Nl#-}em`Ss}<_=Smtwv{u(OK^|P6`mG?5_(LBiHl6V8{33FLx@_Llped79EiyiJn-_SXc0j0^Ga~pm z7Td*ry2sfX_12AQpy~9`bOMr8AZL=-Boz`BgO`;88}ZC5^I`ZOF(0!(T#=%q{I&#w zg&Z`|3Kq0F00%=zEf492&iAplbJUSs8|2;Derw3ue@x3_8J@#9d%c>SQ z`ucJJXin#l#K(mo@dn$osmvV`rZnZyU+&&!#H&Gk^oy8;3EYS@x}xIdoYv6y&-C;7zWk6pj4?W z3xIv7`8WGO|0x`!Ls&X3e@PX*X9m`p;ty*U@^P_;j2MY6+lcHE8B4nnMBNy3M=rDI zl7232J&kg++^ETAWmv%lFMYV>*qAud`Pn(i`1wl|ko$v&jQt0}vm^W(3B#!1qyT+$ z4&jOwS}1)7Hf9r%bqaQ2L--PdL*eBg_ff8)Y~x1&4L&(v#9M1(DC5`-RdAnGW(N1E zHw-BA=AGt(U;Lwn>l)G3BIvuMk!mx`MuXv*Iq*GODE}iB+8FsaqAet4l6ZVWXPaWB z)k}e(z%J(Uj%t+;W|+Dc_?9aI{PwFa@F9YyA8M;%8SZM%+j$S_LdT*8OC9t;*8P9x z0IhYF`d@RLv7M8YF`Nt%t&0)pu`6V&vQS0MR#4XC_4fYj)lzE$B5_0xl zy44jvNiIyQm!QSdZKw4G1KoSo(8LHy_5Ay`r}MNqW+qX*#(sOqT2xmBfILor{zr~0 zgzLZFtzYdsFu^a7X|pAEG>`=*W#mykkdtAXJ{p<3jL11bCtY36HeBN?aWM=TXWM-` z^}D?w7G=m@>7-@ zO304F%vrGIWfTW<2WwQ~@a@{1b5U3rl;IP(#PO zs6vj8xf+E3;*14^Ny*3%@2~f_>Y4B}m%9yzWH)9KYTr49xv&NNPJj*J(wa1wr`s4o z&&dMP>u)$b?RG;Y@xrnY(XEBW6PGnMBx3SrK=#U#VqT_q#)FFC_ z<1i{gc*G;C=>Kz8##cyU&gJ*-Pc~Y>>07N=8Bv4HWHga^#ar=2z70WvQkhIY9ASZt z;gu9|Nxl421ez{Dxya&*uptduIWi}VU}{l}ILHkaTGLowxyJ!ThY$$x0-1?}D<-Hy z#>9`l2RYiEfz+sr2HDZEN`(qZ0p}^l6c=%3`!Mh*6)t3u-ET#}w@Jc?qp=2}Rp9M&= z5uJ=f$-IF`)@8q7%VK?H>zJU9t7hajV;c$|It0K6-Vsl>up1*Jh4&qD8zV389yWm+ z2rAYoq=G6u8_)7-K)GCd8fu9yHK^{^<+81={ux(}p@N6pXZdiTOs>6!3TCjWpuEQd z#l~EsiRPTDy9@h)WiT%AeLuwx?0pxyd%Yq#ro1z=2`&C*>iaI2p! z6J0L;>+0Gh@wN^QZS~sE_Rq<&N+_2#Ms2+S_&d|{s&4x21&*-$42{5X zZ*YaX=;J-UlKJ9Akfbv|DuOQ+w9j2cNx4-3{o~lE17ME--r52Vfp`9o#a7Ma9uK~)}-1C4^uLx@k z;6PgLiZu#E8)E|6wFNXFJ>bfkXk)5<`8MY}sDeeYwjD+9eDAyfxb@IoP-|7)EXn^U?Y1@@5Pa8~ zIh}>?d)bHc$;7nx<-kF~kjW@=yg4^P&78^lyp|{a2Hm$ZqY7BFgV?Q{%U4>&EZDHJ+ih4uki7lL;Qgdix4&0VCS z0+fwm?@4okKsTJCM_(_saclSo%zV-g+Vm(uC#Uf;{ z_sBu1k~VjFHtsIDH<@~cXEM3Ru4r#Nyt2DXBtj*DyOy$ojqxsZb&^~z5Cw)9FufC$ z|ER!)2m=44W3(~l6n9%I3aU_8uPv>*J~NqKKxL}@mEIN zduPD+)lF#PnUM5*}|@oQ>V&A5(+}o{jj{Cx%xX z)P1Qgl0Q%WNf)GKgZPa1S4-pIe~EM9-`D;EONFZ21dN;wdZ?a+{qUfI>(_C7)V(gY z=Dai=@F1x#Ek3%{hnXM>ivGlUX)4va5Ut4e`dnVE$;UsEU!My{?r;zzdiLw-NIYOg z$7{-&!Nx?_?TxJ9T_mhBbN^ews(WiE-(JEMUz^6>)}i69UV;E$xpZ0yOyO8Dzorp- zt()jfgilfmEC3HAhQQ@Bv9VG$D&6 zrGFoR1vnbf|93|-d}eaZ%v0KFXRTAKf&6b&BHB3&0r;)4Kd9=W#x6P}l8f<>6rioZ zVR*(=FM5szd~75vC}S8skXF|GdvBt@C&8ED#9+F^|LJitl4JK7J$f_=Midc@gUrZ% z^TWlyk22|VNQb_`P*Vkf{HeRrL_FTGx%YX}{6mKt17M8gzk@N2DikZ3Nxuv&7mHL= z5Ca0^vDC_+MdJn{impU07sjCozM`5Xb4+Uh!Z1#SaJju`NJJ_yTU-)TdU-Ky%w<0 z{?Ab9wSYwq$?Y$BOrZ;;_Sw1@n?<2y+|LcB87R7N5f~%UYpK`%-n=KgqTKB7%>!$_ zmw=mxS9EC7tUd;odM{C}J_1)-G|!KbmEKFh2SA``(Fj0Cy69I(KEA=R_SP8z5cnL> zKlb0N2~35@UJ({rGP}gz>!XE|Q2sb0jTXNq#48Pee0hXmkrG6}WOf1Z8$$Tw6bYGJ%AX!y)K<76#hURmfWB#&Jan9@yl% z`M;k&kTYl6OMZ>4B3yvS8i6-IL42{gZLkR!eJ65;5B2dK?H3trR%+FY8-BWVQLpy>^Me`e(s&q zE5o#Y7MblherpX}t(9SVKTFJa90MP{T;pY!nZ6Qym2(tc(BTao<;DOmKl~frLDJ*K zr@*G8wjmo#-V3`}AO?tw3Cmb=vnQMerL4$g8?10O^M{V%K}tdQ|8}M7=s=X?*tTos z_f0DI%$_<6-!3*{Fcuaau;M<67D~jl3Jlp3TXoQxaHG|$={#dC%4H7TljnDBnoTgK zeO{;Q;W0+C(MA&>FlZ(YQ0a~d=2uj!L>zp+`)z?qRIIK34Xc)uqxgi*==KhM$q`+_ z99Ucb1T;qvqVXmMous^rz*6mrzbMy=j?a~f4GRfLGnzfnVOolGmEKHU!>KlN%_R+T{}kca&olm-iKiw=hVC|I#lqndimdNOvS|Ni3e zEo7#f(Z6Q_ychiL(rsw)vPp)z)`MZ2p5=5(U$|NilTs|rf08}n5#-_!#NIwf3m+X} zJ8B{W6d?p0qC^uK1RXG%O{quGiArD{76@(_EBuC%134$msKUu{?QUHSrIp zldTMl+ex}@(&X`7=*0@qd-q9Nr%&j*lI{3aEnId}GtBkVV_Jy;^HiuH96OjlNK|_i13i8bGld=zy!513 zxOg6(>mwh+FJrsVLn^TQDMWVEuMfKAtmTEKkY%-8SyLLZr;4*`tqMJr0}5bOv#Ts< z;$B)HQi}OsGT=F$4cOp>4hj#ms83mF^`4q!A~Yn#WT8OoEllP8v}q}QBAx@Ph0AAU zT4@XRsUE+%(bE?RQp{wsjr%JauWJ^M8$Wq8=aK0rXG8^_>=dw%A-uCJMrumE0XdRD zVT9^@+13%Czb{!`G|y^h)4q~Pm4}jHBFE|N$=xJ7T;8?$JPc!49zENKKi{yWQ4lJ8 zkI*1InPtBf276)cx9ErRs)_Ox%mJg4E3|4W9pCW3l)k7&Jy48)KS!g*Mc$QTBRf7p zt6*=H9V^-06r&brZrHqv-KymB-SSrmWTsi&offl~quUy79j9v(r3naJhwH72VRhi! z8fpH-5xQ@C2ZtWNtkdj?0mlk|1i~QIP9L zkeGK{KF{SwV=MtO@U?h90!H3$zU9iC0h@U>iV zC|-21W!Dvc3ba*EWR5vw94#TwRoRzIO%Pr�YUZ+W+&LkQo$O{p*SX)aRxxScBX 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 W#nlExJv1K=nv5V(kBi^p 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�O(3^#vXZtaXtiCioVm*Q|Ktom* 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
r~uo-X!#mK&qx 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

? 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%0DL`aWmbE?C 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Ⓢl~Mt5u(U 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 kh_RCY3iOXsg{*%_G8Uiy9|<^e)c^nh literal 0 HcmV?d00001 diff --git a/rfcs/images/15_clustering/perf-clustering-2-worker.png b/rfcs/images/15_clustering/perf-clustering-2-worker.png new file mode 100644 index 0000000000000000000000000000000000000000..a767de58eba69a070ca1bf064caac78cfc442007 GIT binary patch literal 450498 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�!n+(CekD?pZkRhRYwgswEh)2apKNZ^bwx3p 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

fjN2JvWo7@PD*m+Rqw_pgm_B`&WV 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 z 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

) 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�cK)N`35`kf!b|kBtukVT2@VoI-JJ=&3Gdg^ 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 &KQNDI6_%5_|cpefKm_p9sMyl}DY(ob`OUeV^) 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≫*GYY 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%0D80tvy{fXKSJg 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 =AR(Z)(3nhSLGaumii#`EiN_aG&^DqK0%w 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

%VtQk$8 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 P0zY0fUk}m7ohg3;UnS#Y literal 0 HcmV?d00001 diff --git a/rfcs/text/0020_nodejs_clustering.md b/rfcs/text/0020_nodejs_clustering.md new file mode 100644 index 0000000000000..62c4de4d3cbe4 --- /dev/null +++ b/rfcs/text/0020_nodejs_clustering.md @@ -0,0 +1,729 @@ +- Start Date: 2021-03-09 +- RFC PR: https://github.com/elastic/kibana/pull/94057 +- Kibana Issue: https://github.com/elastic/kibana/issues/68626 +- POC PR: https://github.com/elastic/kibana/pull/93380 + +--- + +- [1. Summary](#1-summary) +- [2. Motivation](#2-motivation) +- [3. Architecture](#3-architecture) +- [4. Testing](#4-testing) +- [5. Detailed design](#5-detailed-design) +- [6. Technical impact](#6-technical-impact) + - [6.1 Technical impact on Core](#6.1-technical-impact-on-core) + - [6.2 Technical impact on Plugins](#6.2-technical-impact-on-plugins) + - [6.3 Summary of breaking changes](#6.3-summary-of-breaking-changes) +- [7. Drawbacks](#7-drawbacks) +- [8. Alternatives](#8-alternatives) +- [9. Adoption strategy](#9-adoption-strategy) +- [10. How we teach this](#10-how-we-teach-this) +- [11. Unresolved questions](#11-unresolved-questions) +- [12. Resolved questions](#12-resolved-questions) + +# 1. Summary + +This RFC proposes a new core service which leverages the [Node.js cluster API](https://nodejs.org/api/cluster.html) +to support multi-process Kibana instances. + +# 2. Motivation + +The Kibana server currently uses a single Node process to serve HTTP traffic. +This is a byproduct of the single-threaded nature of Node's event loop. + +As a consequence, Kibana cannot take advantage of multi-core hardware: If you run Kibana on an +8-core machine, it will only utilize one of those cores. This makes it expensive to scale out +Kibana, as server hardware will typically have multiple cores, so you end up paying for power +you never use. Since Kibana is generally more CPU-intensive than memory-intensive, it would be +advantageous to use all available cores to maximize the performance we can get out of a single +machine. + +Another benefit of this approach would be improving Kibana's overall performance for most users +without requiring an operator to scale out the server, as it would allow the server to handle +more http requests at once, making it less likely that a single bad request could delay the +event loop and impact subsequent requests. + +The introduction of a clustering mode would allow spawning multiple Kibana processes ('workers') +from a single Kibana instance. (See [Alternatives](#8-alternatives) to learn more about the +difference between clustering and worker pools). You can think of these processes as individual +instances of the Kibana server which listen on the same port on the same machine, and serve +incoming traffic in a round-robin fashion. + +Our intent is to eventually make clustering the default behavior in Kibana, taking advantage of +all available CPUs out of the box. However, this should still be an optional way to run Kibana +since users might have use cases for single-process instances (for example, users running Kibana +inside Docker containers might choose to rather use their container orchestration to run a +container per host CPU with a single Kibana process per container). + +# 3. Architecture + +In 'classic' mode, the Kibana server is started in the main Node.js process. + +![image](../images/15_clustering/no-cluster-mode.png) + +In clustering mode, the main Node.js process would only start the coordinator, which would then +fork workers using Node's `cluster` API. Node's underlying socket implementation allows multiple +processes to listen to the same ports, effectively performing http traffic balancing between the +workers for us. + +![image](../images/15_clustering/cluster-mode.png) + +The coordinator's primary responsibility is to orchestrate the workers. It would not be a 'super' +worker handling both the job of a worker while being in charge of managing the other workers. + +In addition, the coordinator would be responsible for some specific activities that need to be +handled in a centralized manner: +- collecting logs from each of the workers & writing them to a single file or stdout +- gathering basic status information from each worker for use in the `/status` and `/stats` APIs + +Over time, it is possible that the role of the coordinator would expand to serve more purposes, +especially if we start implementing custom routing logic to run different services on specialized +processes. + +# 4. Testing + +Thorough performance testing is critical in evaluating the success of this plan. The results +below reflect some initial testing that was performed against an experimental +[proof-of-concept](https://github.com/elastic/kibana/pull/93380). Should we move forward with this +RFC, one of the first tasks will be to update the POC and build out a more detailed test plan that +covers all of the scenarios we are concerned with. + +## 4.1 Local testing + +These tests were performed against a local development machine, with an 8-core CPU(2.4 GHz 8-Core +Intel Core i9 - 32 GB 2400 MHz DDR4), using the default configuration of the `kibana-load-testing` tool. + +### 4.1.1 Raw results + +#### Non-clustered mode + +![image](../images/15_clustering/perf-no-clustering.png) + +#### Clustered mode, 2 workers + +![image](../images/15_clustering/perf-clustering-2-worker.png) + +#### Clustered mode, 4 workers + +![image](../images/15_clustering/perf-4-workers.png) + +### 4.1.2 Analysis + +- Between non-clustered and 2-worker cluster mode, we observe a 20/25% gain in the 50th percentile response time. + Gain for the 75th and 95th are between 10% and 40% +- Between 2-worker and 4-workers cluster mode, the gain on 50th is negligible, but the 75th and the 95th are + significantly better on the 4-workers results, sometimes up to 100% gain (factor 2 ratio) + +Overall, switching to 2 workers comes with the most significant improvement in the 50th pct, +and increasing further to 4 workers decreases even more significantly the highest percentiles. +Even if increasing the number of workers doesn’t just linearly increase the performances +(which totally make sense, most of our requests response time is caused by awaiting ES response), +the improvements of the clustering mode on performance under heavy load are far from negligible. + +## 4.2 Testing against cloud + +There is currently no easy way to test the performance improvements this could provide on Cloud, as we can't +deploy custom builds or branches on Cloud at the moment. + +On Cloud, Kibana is running in a containerised environment using CPU CFS quota and CPU shares. + +If we want to investigate the potential perf improvement on Cloud further, our only option would be to setup a +similar-ish environment locally (which wasn't done during the initial investigation). + +# 5. Detailed design + +## 5.1 Enabling clustering mode + +Enabling clustering mode will be done using the `node.enabled` configuration property. + +If clustering is enabled by default, then no configuration would be required by users, and +Kibana would automatically use all available cores. However, more detailed configuration +would be available for users with more advanced use cases: +```yaml +node: + enabled: true # enabled by default + + coordinator: + max_old_space_size: 1gb # optional, allows to configure memory limit for coordinator only + + # Basic config for multiple workers with the same options + workers: # when count is provided, all workers share the same config + count: 2 # worker names (for logging) are generated: `worker-1`, `worker-2` + max_old_space_size: 1gb # optional, allows to configure memory limits per-worker + + # Alternative advanced config, allowing for worker "types" to be configured + workers: + foo: # the key here would be used as the worker name + count: 2 + max_old_space_size: 1gb + bar: + count: 1 + max_old_space_size: 512mb +``` + +This per-worker design would give us the flexibility to eventually provide more fine-grained configuration, +like dedicated workers for http requests or background jobs. + +## 5.2 Cross-worker communication + +For some of our changes (such as the `/status` API, see below), we will need some kind of cross-worker +communication. This will need to pass through the coordinator, which will also serve as an 'event bus', +or IPC forwarder. + +This IPC API will be exposed from the node service: + +```ts +export interface NodeServiceSetup { + // [...] + broadcast: (type: string, payload?: WorkerMessagePayload, options?: BroadcastOptions) => void; + addMessageHandler: (type: string, handler: MessageHandler) => MessageHandlerUnsubscribeFn; +} +``` + +To preserve isolation and to avoid creating an implicit cross-plugin API, handlers registered from a +given plugin will only be invoked for messages sent by the same plugin. + +Notes: +- To reduce clustered and non-clustered mode divergence, in non-clustered mode, these APIs would just be no-ops. + It will avoid forcing (most) code to check which mode Kibana is running before calling them. + - In the case where `sendToSelf` is true, we would still attempt to broadcast the message. +- We could eventually use an Observable pattern instead of a handler pattern to subscribe to messages. + +## 5.3 Executing code on a single worker + +In some scenarios, we would like to have parts of the code executed only from a single process. + +Saved object migrations would be a good example: +we don't need to have each worker try to perform the migration, and we'd prefer to have one performing/trying +the migration, and the others waiting for it. Due to the architecture, we can't have the coordinator perform +such single-process jobs, as it doesn't actually run a Kibana server. + +There are various ways to address such use-cases. What seems to be the best compromise right now would be the +concept of 'main worker'. The coordinator would arbitrarily elect a worker as the 'main' one at startup. The +node service would then expose an API to let workers identify themselves as main or not. + +```ts +export interface NodeServiceSetup { + // [...] + isMainWorker: () => boolean; +} +``` + +Notes: +- In non-clustered mode, `isMainWorker` would always return true, to reduce the divergence between clustered and + non-clustered modes. + +## 5.4 The node service API + +We propose adding a new node service to Core, which will be responsible for adding the necessary cluster APIs, +and handling interaction with Node's `cluster` API. This service would be accessible via Core's setup and start contracts +(`coreSetup.node` and `coreStart.node`). + +At the moment, no need to extend Core's request handler context with node related APIs has been identified. + +The initial contract interface would look like this: + +```ts +type WorkerMessagePayload = Serializable; + +interface BroadcastOptions { + /** + * If true, will also send the message to the worker that sent it. + * Defaults to false. + */ + sendToSelf?: boolean; + /** + * If true, the message will also be sent to subscribers subscribing after the message was effectively sent. + * Defaults to false. + */ + persist?: boolean; +} + +export interface NodeServiceSetup { + /** + * Return true if clustering mode is enabled, false otherwise + */ + isEnabled: () => boolean; + /** + * Return the current worker's id. In non-clustered mode, will return `1` + */ + getWorkerId: () => number; + /** + * Broadcast a message to other workers. + * In non-clustered mode, this is a no-op. + */ + broadcast: (type: string, payload?: WorkerMessagePayload, options?: BroadcastOptions) => void; + /** + * Registers a handler for given `type` of IPC messages + * In non-clustered mode, this is a no-op that returns a no-op unsubscription callback. + */ + addMessageHandler: (type: string, handler: MessageHandler) => MessageHandlerUnsubscribeFn; + /** + * Returns true if the current worker has been elected as the main one. + * In non-clustered mode, will always return true + */ + isMainWorker: () => boolean; +} +``` + +### 5.4.1 Example: Saved Object Migrations + +To take the example of SO migration, the `KibanaMigrator.runMigrations` implementation could change to +(naive implementation, the function is supposed to return a promise here, did not include that for simplicity): + +```ts +runMigration() { + if (node.isMainWorker()) { + this.runMigrationsInternal().then((result) => { + applyMigrationState(result); + // persist: true will send message even if subscriber subscribes after the message was actually sent + node.broadcast('migration-complete', { payload: result }, { persist: true }); + }) + } else { + const unsubscribe = node.addMessageHandler('migration-complete', ({ payload: result }) => { + applyMigrationState(result); + unsubscribe(); + }); + } +} +``` + +Notes: + - To be sure that we do not encounter a race condition with the event subscribing / sending (workers subscribing after + the main worker actually sent the `migration-complete` event and then waiting indefinitely), we are using the `persist` + option of the `broadcast` API. We felt this was a better approach than the alternative of having shared state among workers. + +## 5.5 Sharing state between workers + +This is not identified as necessary at the moment, and IPC broadcast should be sufficient, hopefully. We prefer to avoid +the added complexity and risk of implicit dependencies if possible. + +If we do eventually need shared state, we would probably have to use syscall libraries to share buffers such as +[mmap-io](https://www.npmjs.com/package/mmap-io), and expose a higher level API for that from the `node` service. More +research would be required if this proved to be a necessity. + +# 6. Technical impact + +This section attempts to be an exhaustive inventory of the changes that would be required to support clustering mode. + +## 6.1 Technical impact on Core + +### 6.1.1 Handling multi-process logs + +This is an example of log output in a 2 workers cluster, coming from the POC: + +``` +[2021-03-02T10:23:41.834+01:00][INFO ][plugins-service] Plugin initialization disabled. +[2021-03-02T10:23:41.840+01:00][INFO ][plugins-service] Plugin initialization disabled. +[2021-03-02T10:23:41.900+01:00][WARN ][savedobjects-service] Skipping Saved Object migrations on startup. Note: Individual documents will still be migrated when read or written. +[2021-03-02T10:23:41.903+01:00][WARN ][savedobjects-service] Skipping Saved Object migrations on startup. Note: Individual documents will still be migrated when read or written. +``` + +The workers logs are interleaved, and, most importantly, there is no way to see which process each log entry is coming from. +We will need to address that. + +#### Options we considered: + +1. Having a distinct logging configuration (with separate log files) for each worker +2. Centralizing log collection in the coordinator and writing all logs to a single file (or stdout) + +#### Our recommended approach: + +Overall we recommend keeping a single log file (option 2), and centralizing the logging system in the coordinator, +with each worker sending the coordinator log messages via IPC. While this is a more complex implementation in terms +of our logging system, it solves several problems: +- Preserves backwards compatibility. +- Avoids the issue of interleaved log messages that could occur with multiple processes writing to the same file or stdout. +- Provides a solution for the rolling-file appender (see below), as the coordinator would handle rolling all log files +- The changes to BaseLogger could potentially have the added benefit of paving the way for our future logging MDC. + +We could add the process name information to the log messages, and add a new conversion to be able to display it with +the pattern layout, such as `%worker` for example. + +The default pattern could evolve to (ideally, only when clustering is enabled): +``` +[%date][%level][%worker][%logger] %message +``` + +The logging output would then look like: +``` +[2021-03-02T10:23:41.834+01:00][INFO ][worker-1][plugins-service] Plugin initialization disabled. +[2021-03-02T10:23:41.840+01:00][INFO ][worker-2][plugins-service] Plugin initialization disabled. +``` + +Notes: +- The coordinator will probably need to output logs too. `%worker` would be interpolated to `coordinator` + for the coordinator process. +- Even if we add the `%worker` pattern, we could still consider letting users configure per-worker log +files as a future enhancement. + +### 6.1.2 The rolling-file appender + +The rolling process of the `rolling-file` appender is going to be problematic in clustered mode, as it will cause +concurrency issues during the rolling. We need to find a way to have this rolling stage clustered-proof. + +#### Options we considered: + +1. have the rolling file appenders coordinate themselves when rolling + +By using a broadcast message based mutex mechanism, the appenders could acquire a ‘lock’ to roll a specific file, and +notify other workers when the rolling is complete (quite similar to what we want to do with SO migration for example). + +An alternative to this option would be to only have the main worker handle the rolling logic. We will lose control +on the exact size the file is when rolling, as we would need to wait until the main worker receives a log message +for the rolling appender before the rolling is effectively performed. The upside would be that it reduces the inter-workers +communication to a notification from the main worker to the others once the rolling is done for them to reopen their +file handler. + +2. have the coordinator process perform the rolling + +Another option would be to have the coordinator perform the rotation instead. When a rolling is required, the appender +would send a message to the coordinator, which would perform the rolling and notify the workers once the operation is complete. + +Note that this option is even more complicated than the previous one, as it forces to move the rolling implementation +outside of the appender, without any significant upsides identified. + +3. centralize the logging system in the coordinator + +We could go further, and change the way the logging system works in clustering mode by having the coordinator centralize +the logging system. The worker’s logger implementation would just send messages to the coordinator. If this may be a +correct design, the main downside is that the logging implementation would be totally different in cluster and +non cluster mode, and seems to be way more work that the other options. + +#### Our recommended approach: +Even though it's more complex, we feel that centralizing the logging system in the coordinator is the right move here, +as it will also solve for how to enable the coordinator to log its own messages. + +### 6.1.3 The status API + +In clustering mode, the workers will all have an individual status. One could have a connectivity issue with ES +while the other ones are green. Hitting the `/status` endpoint will reach a random (and different each time) worker, +meaning that it would not be possible to know the status of the cluster as a whole. + +We will need to add some centralized status state in the coordinator. Also, as the `/status` endpoint cannot be served +from the coordinator, we will also need to have the workers retrieve the global status from the coordinator to serve +the status endpoint. + +Ultimately, we'd need to make the following updates to the `/status` API, neither of which +is a breaking change: +1. The response will return the highest-severity status level for each plugin, which will be +determined by looking at the shared global status stored in the coordinator. +2. We will introduce an extension to the existing `/status` response to allow inspecting +per-worker statuses. + +### 6.1.4 The stats API & metrics service + +The `/stats` endpoint is somewhat problematic in that it contains a handful of `process` metrics +which will differ from worker-to-worker: +```json +{ + // ... + "process": { + "memory": { + "heap": { + "total_bytes": 533581824, + "used_bytes": 296297424, + "size_limit": 4345298944 + }, + "resident_set_size_bytes": 563625984 + }, + "pid": 52646, + "event_loop_delay": 0.22967800498008728, + "uptime_ms": 1706021.930404 + }, + // ... +} +``` + +As each request could be routed to a different worker, different results may come back each time. + +This endpoint, registered from the `usage_collection` plugin, is getting these stats from Core's +`metrics` service (`getOpsMetrics$`), which is also used in the `monitoring` plugin for stats +collection. + +Ultimately we will extend the API to provide per-worker stats, but the question remains what we +should do with the existing `process` stats. + +#### Options we considered: +1. Deprecate them? (breaking change) +2. Accept a situation where they may be round-robined to different workers? (probably no) +3. Try to consolidate them somehow? (can't think of a good way to do this) +4. Always return stats for one process, e.g. main or coordinator? (doesn't give us the full picture) + +#### Our recommended approach: +We agreed that we would go with (3) and have each worker report metrics to the coordinator for +sharing, with the metrics aggregated as follows: +```json +{ + // ... + "process": { + "memory": { + "heap": { + "total_bytes": 533581824, // sum of coordinator + workers + "used_bytes": 296297424, // sum of coordinator + workers + "size_limit": 4345298944 // sum of coordinator + workers + }, + "resident_set_size_bytes": 563625984 // sum of coordinator + workers + }, + "pid": 52646, // pid of the coordinator + "event_loop_delay": 0.22967800498008728, // max of coordinator + workers + "uptime_ms": 1706021.930404 // uptime of the coordinator + }, + // ... +} +``` + +This has its downsides (`size_limit` in particular could be confusing), but otherwise generally makes sense: +- sum of available/used heap & node rss is straightforward +- `event_loop_delay` max makes sense, as we are mostly only interested in that number if it is high anyway +- `pid` and `uptime_in_millis` from the coordinator make sense, especially as long as we are killing +all workers any time one of them dies. In the future if we respawn workers that die, this could be +misleading, but hopefully by then we can deprecate this and move Metricbeat to using the per-worker +stats. + +### 6.1.5 PID file + +Without changes, each worker is going to try to write and read the same PID file. Also, this breaks the whole pid file +usage, as the PID stored in the file will be a arbitrary worker’s PID, instead of the coordinator (main process) PID. + +In clustering mode, we will need to have to coordinator handle the PID file logic, and to disable pid file handling +in the worker's environment service. + +### 6.1.6 Saved Objects migration + +In the current state, all workers are going to try to perform the migration. Ideally, we would have only one process +perform the migration, and the other ones just wait for a ready signal. We can’t easily have the coordinator do it, +so we would probably have to leverage the ‘main worker’ concept here. + +The SO migration v2 is supposed to be resilient to concurrent attempts though, as we already support multi-instances +Kibana, so this can probably be considered an improvement. + +### 6.1.7 Memory consumption + +In clustered mode, node options such as `max-old-space-size` will be used by all processes. + +The `kibana` startup script will read this setting out of the CLI or `config/node.options` and set a NODE_OPTIONS environment +variable, which will be passed to any workers, possibly leading to unexpected behavior. + +e.g. using `--max-old-space-size=1024` in a 2 workers cluster would have a maximum memory usage of 3gb (1 coordinator + 2 workers). + +Our plan for addressing this is to _disable clustering if a user has `max-old-space-size` set at all_, which would ensure it isn't +possible to hit unpredictable behavior. To enable clustering, the user would simply remove `max-old-space-size` settings, and +clustering would be on by default. They could alternatively configure memory settings for each worker individually, as shown above. + +### 6.1.8 Workers error handling + +When using `cluster`, the common best practice is to have the coordinator recreate ('restart') workers when they terminate unexpectedly. +However, given Kibana's architecture, some failures are not recoverable (workers failing because of config validation, failed migration...). + +For instance, if a worker (well, all workers) terminates because of an invalid configuration property, it doesn't make +any sense to have the coordinator recreate them indefinitely, as the error requires manual intervention. + +As a first step, we plan to terminate the main Kibana process when any worker terminates unexpectedly for any reason (after all, +this is already the behavior in non-cluster mode). In the future, we will look toward distinguishing between recoverable +and non-recoverable errors as an enhancement, so that we can automatically restart workers on any recoverable error. + +### 6.1.9 Data folder + +The data folder (`path.data`) is currently the same for all workers. + +We still have to identify with the teams if this is going to be a problem. It could be, for example, if some plugins +are accessing files in write mode, which could result in concurrency issues between the workers. + +If that was confirmed, we would plan to create and use a distinct data folder for each worker, which would be non-breaking +as we don't consider the layout of this directory to be part of our public API. + +### 6.1.10 instanceUUID + +The same instance UUID (`server.uuid` / `{dataFolder}/uuid`) is currently used by all the workers. + +So far, we have not identified any places where this will be problematic, however, we will look to other teams to +help validate this. + +Note that if we did need to have per-worker UUIDs, this could be a breaking change, as the single `server.uuid` +configuration property would not be enough. If this change becomes necessary, one approach could be to have unique worker +IDs with `${serverUuid}-${workerId}`. + +## 6.2 Technical impact on Plugins + +### 6.2.1 What types of things could break? + +#### Concurrent access to the same resources + +Is there, for example, some part of the code that is accessing and writing files from the data folder (or anywhere else) +and makes the assumption that it is the sole process actually writing to that file? + +#### Using instanceUUID as a unique Kibana process identifier + +Is there, for example, schedulers that are using the instanceUUID a single process id, in opposition to a single +Kibana instance id? Are there situations where having the same instance UUID for all the workers is going to be a problem? + +#### Things needing to run only once per Kibana instance + +Is there any part of the code that needs to be executed only once in a multi-worker mode, such as initialization code, +or starting schedulers? + +An example would be Reporting's queueFactory polling. As we want to only be running a single headless at a time per +Kibana instance, only one worker should have polling enabled. + +### 6.2.2 Identified required changes + +#### Reporting + +We will probably want to restrict to a single headless per Kibana instance. For that, we will have to change the logic +in [createQueueFactory](https://github.com/elastic/kibana/blob/4584a8b570402aa07832cf3e5b520e5d2cfa7166/x-pack/plugins/reporting/server/lib/create_queue.ts#L60-L64) +to only have the 'main' worker be polling for reporting tasks. + +#### Telemetry + +- Server side fetcher + +The telemetry/server/fetcher.ts will attempt sending the telemetry usage multiple times once per day from each process. +We do store a state in the SavedObjects store of the last time the usage was sent to prevent sending multiple times +(although race conditions might occur). + +- Tasks storing telemetry data + +We have tasks across several plugins storing data in savedobjects specifically for telemetry. Under clustering these +tasks will be registered multiple times. + +Note that sending the data multiple times doesn’t have any real consequences, apart from the additional number of ES requests, +so this should be considered non-blocking and only an improvement. + +- Event-based telemetry + +Event-based telemetry may be affected as well. Both the existing one in the Security Solutions team and the general +one that is in the works. More specifically, the size of the queues will be multiplied per worker, also growing in the +amount of network bandwidth used, and potentially affecting our customers. + +We could address that by making sure that the queues are held only in the main worker. + +#### Task Manager + +Currently, task manager does "claims" for jobs to run based on the server uuid. We think this could still work with +a multi-process setup - each task manager in the worker would be doing "claims" for the same server uuid, which +seems functionally the same as setting max_workers to `current max_workers * number of workers`. +Another alternative would be to compose something like `${server.uuid}-${worker.Id}`, as TM only +really needs a unique identifier. + +However, as a first step we can simply run Task Manager on the main worker. This doesn't completely solve potential +noisy neighbor problems as the main worker will still be receiving & serving http requests, however it will at least +ensure that other worker processes are free to serve http requests without risk of TM interference. Long term, we +could explore manually spawning a dedicated child process for background tasks that can be called from workers, and +thinking of a way for plugins to tell Core when they need to run things in the background. + +It would be ideal if we could eventually solve this with our multi-process setup, however this needs more design work +and could necessitate an RFC in its own right. The key thing to take away here is that the work we are doing in this +RFC would not prevent us from exploring this path further in a subsequent phase. In fact, it could prove to be a +helpful first step in that direction. + +#### Alerting + +Currently haven't identified any Alerting-specific requirements that aren't already covered by the +Task Manager requirements. + +## 6.3 Summary of breaking changes + +### 6.3.1 `/stats` API & metrics service + +Currently the only breaking change we have identified is for the `/stats` API. + +The `process` memory usage reported doesn't really make sense in a multi-process Kibana, and +even though we have a plan to aggregate this data as a temporary solution (see 6.1.4), this +could still lead to confusion for users as it doesn't paint a clear picture of the state of the system. + +Our plan is to deprecate the `process` field, and later remove it or change the structure +to better support a multi-process Kibana. + +# 7. Drawbacks + +- Implementation cost is going to be significant as this will require multiple phases, both in core and in plugins. + Also, this will have to be a collaborative effort, as we can't enable cluster mode in production until all of the + identified breaking changes have been addressed. +- Even if it is easier to deploy, at a technical level it doesn't really provide anything more than a multi-instance Kibana setup. +- This will add complexity to the code, especially in Core where some parts of the logic will drastically diverge between + clustered and non-clustered modes (most notably our logging system). +- There is a risk of introducing subtle bugs in clustered mode, as we may overlook some breaking changes, or developers + may neglect to ensure clustered mode compatibility when adding new features. +- Proper testing of all the edge cases is going to be tedious, and in some cases realistically impossible. Proper + education of developers is going to be critical to ensure we are building new features with clustering in mind. + +# 8. Alternatives + +One alternative to the `cluster` module is using a worker pool via `worker_threads`. Both have distinct use cases +though. Clustering is meant to have multiple workers with the same codebase, often sharing a network socket to balance +network traffic. Worker threads is a way to create specialized workers in charge of executing isolated, CPU intensive +tasks on demand (e.g. encrypting or descrypting a file). If we were to identify that under heavy load, the actual bottleneck +is ES, maybe exposing a worker thread service and API from Core (task_manager would be a perfect example of potential consumer) +would make more sense. + +However, we believe the simplicity and broad acceptance of the `cluster` API in the Node community makes it the +better approach over `worker_threads`, and would prefer to only go down the road of a worker pool as a last resort. + +Another alternative would be to provide tooling to ease the deployment of multi-instance Kibana setups, and only support +multi-instance mode moving forward. + +# 9. Adoption strategy + +Because the changes proposed in this RFC touch the lowest levels of Kibana's core, and therefore have potential to impact +large swaths of Kibana's codebase, we propse a multi-phase strategy: + +## Phase 0 +In the prepratory phase, we will evolve the existing POC to validate the finer details of this RFC, while also putting together +a more detailed testing strategy that can be used to benchmark our future work. + +## Phase 1 +To start implementation, we will make the required changes in Core, adding the `node.enabled` configuration property. +At first, we'll include a big warning in the logs to make it clear that this shouldn't be used in production yet. +This way, we allow developers to test their features against clustering mode and to adapt their code +to use the new `node` API and service. At this point we will also aim to document any identified breaking changes and +add deprecation notices where applicable, to allow developers time to prepare for 8.0. + +## Phase 2 +When all the required changes have been performed in plugin code, we will enable the `node` configuration on production +mode as a `beta` feature. We would ideally also add telemetry collection for the clustering usages (relevant metrics TBD) +to have a precise vision of the adoption of the feature. + +## Phase 3 +Once the new feature has been validated and we are comfortable considering it GA, we will enable `node` by default. +(We could alternatively enable it by default from the outset, still with a `beta` label). + +# 10. How we teach this + +During Phase 1, we should create documentation on the clustering mode: best practices, how to identify code that may break in +clustered mode, and so on. + +We will specifically look to make changes to our docs around contributing to Kibana, specifically we can add a section +in the [best practices](https://www.elastic.co/guide/en/kibana/master/development-best-practices.html#_testing_stability) to +remind contributers to be thinking about the fact that you cannot rely on a 1:1 relationship between the Kibana process and +an individual machine. + +Lastly, we'll take advantage of internal communications to kibana-contributors, and make an effort to individually check in +with the teams who we think will most likely be affected by these changes. + +# 11. Unresolved questions + +**Are breaking changes required for the `/stats` API & metrics service?** + +See 6.1.4 above. + +# 12. Resolved questions + +**How do we handle http requests that need to be served by a specific process?** + +The Node.js cluster API is not really the right solution for this, as it won't allow for custom scheduling policies. A custom scheduling policy would basically mean re-implementing the cluster API on our own. At this point we will not be solving this particular issue with the clustering project, however the abstraction proposed in this RFC will not preclude us from changing out the underlying implementation in the future should we choose to do so.  + +**How do we handle http requests that need to have knowledge of all processes?** + +`/status` and `/stats` are the big issues here, as they could be reported differently from each process. The current plan is to manage their state centrally in the coordinator and have each process report this data at a regular interval, so that all processes can retrieve it and serve it in response to any requests against that endpoint. Exact details of the changes to those APIs would need to be determined. I think `status` will likely require breaking changes as pointed out above, however `stats` may not. + +**Is it okay for the workers to share the same `path.data` directory?** + +We have been unable to identify any plugins which are writing to this directory. +The App Services team has confirmed that `path.data` is no longer in use in the reporting plugin. + +**Is using the same `server.uuid` in each worker going to cause problems?** + +We have been unable to identify any plugins for which this would cause issues. +The Alerting team has confirmed that Task Manager doesn't need server uuid, just a unique +identifier. That means something like server.uuid + worker.id would work. From 73919aa7661446989225b5ab6ab0775252cb3245 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:23:27 -0400 Subject: [PATCH 10/74] [Security Solution] Add Windows and macOs option to dropdown for creating Exceptions from scratch (#103404) --- .../exceptions/add_exception_modal/index.tsx | 21 +++++++++++-------- .../components/exceptions/translations.ts | 7 +++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 288206034a9a0..9d4626fba313f 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -28,7 +28,6 @@ import { import type { ExceptionListType, OsTypeArray, - OsType, ExceptionListItemSchema, CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; @@ -303,10 +302,10 @@ export const AddExceptionModal = memo(function AddExceptionModal({ return alertData !== undefined; }, [alertData]); - const [selectedOs, setSelectedOs] = useState(); + const [selectedOs, setSelectedOs] = useState(); const osTypesSelection = useMemo((): OsTypeArray => { - return hasAlertData ? retrieveAlertOsTypes(alertData) : selectedOs ? [selectedOs] : []; + return hasAlertData ? retrieveAlertOsTypes(alertData) : selectedOs ? [...selectedOs] : []; }, [hasAlertData, alertData, selectedOs]); const enrichExceptionItems = useCallback((): Array< @@ -359,21 +358,25 @@ export const AddExceptionModal = memo(function AddExceptionModal({ return false; }, [maybeRule]); - const OsOptions: Array> = useMemo((): Array< - EuiComboBoxOptionOption + const OsOptions: Array> = useMemo((): Array< + EuiComboBoxOptionOption > => { return [ { label: sharedI18n.OPERATING_SYSTEM_WINDOWS, - value: 'windows', + value: ['windows'], }, { label: sharedI18n.OPERATING_SYSTEM_MAC, - value: 'macos', + value: ['macos'], }, { label: sharedI18n.OPERATING_SYSTEM_LINUX, - value: 'linux', + value: ['linux'], + }, + { + label: sharedI18n.OPERATING_SYSTEM_WINDOWS_AND_MAC, + value: ['windows', 'macos'], }, ]; }, []); @@ -385,7 +388,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ [setSelectedOs] ); - const selectedOStoOptions = useMemo((): Array> => { + const selectedOStoOptions = useMemo((): Array> => { return OsOptions.filter((option) => { return selectedOs === option.value; }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts index f3b697d12af60..c5d1a5faa98f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/translations.ts @@ -274,6 +274,13 @@ export const OPERATING_SYSTEM_MAC = i18n.translate( } ); +export const OPERATING_SYSTEM_WINDOWS_AND_MAC = i18n.translate( + 'xpack.securitySolution.exceptions.operatingSystemWindowsAndMac', + { + defaultMessage: 'Windows and macOS', + } +); + export const OPERATING_SYSTEM_LINUX = i18n.translate( 'xpack.securitySolution.exceptions.operatingSystemLinux', { From 3f0197e32311e5761c89c72bc966a7ec8a115996 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 28 Jun 2021 13:35:10 -0500 Subject: [PATCH 11/74] Revert "[Enterprise Search] Add beta notification (#103429)" This reverts commit 60d1ef9f92226135a7c648259e380513793efd57. --- .../shared/error_state/error_state_prompt.tsx | 10 +- .../applications/shared/layout/beta.scss | 27 ---- .../applications/shared/layout/beta.test.tsx | 131 ------------------ .../applications/shared/layout/beta.tsx | 109 --------------- .../shared/layout/page_template.test.tsx | 2 - .../shared/layout/page_template.tsx | 4 - 6 files changed, 4 insertions(+), 279 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index 5636b56cc33af..f855c7b67dc6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -12,8 +12,7 @@ import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KibanaLogic } from '../kibana'; -import { BetaNotification } from '../layout/beta'; +import { KibanaLogic } from '../../shared/kibana'; import { EuiButtonTo } from '../react_router_helpers'; import './error_state_prompt.scss'; @@ -93,15 +92,14 @@ export const ErrorStatePrompt: React.FC = () => { } - actions={[ + actions={ - , - , - ]} + + } /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss deleted file mode 100644 index 6ba90cba381c4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -@include euiBreakpoint('m', 'l', 'xl') { - .kbnPageTemplateSolutionNav { - position: relative; - min-height: 100%; - - // Nested to override EUI specificity - .betaNotificationSideNavItem { - margin-top: $euiSizeL; - } - } - - .betaNotificationWrapper { - position: absolute; - bottom: 3px; // Without this 3px buffer, the popover won't render to the right - } -} - -.betaNotification { - width: 350px; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx deleted file mode 100644 index 91c3cf8881b8a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx +++ /dev/null @@ -1,131 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import '../../__mocks__/enterprise_search_url.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiPopover, EuiPopoverTitle, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { shallowWithIntl } from '../../test_helpers'; - -import { BetaNotification, appendBetaNotificationItem } from './beta'; - -describe('BetaNotification', () => { - const getToggleButton = (wrapper: ShallowWrapper) => { - return shallow(
{wrapper.prop('button')}
).childAt(0); - }; - - it('renders', () => { - const wrapper = shallow(); - - expect(wrapper.type()).toEqual(EuiPopover); - expect(wrapper.find(EuiPopoverTitle).prop('children')).toEqual( - 'Enterprise Search in Kibana is a beta user interface' - ); - }); - - describe('open/close popover state', () => { - const wrapper = shallow(); - - it('is initially closed', () => { - expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); - }); - - it('opens the popover when the toggle button is pressed', () => { - getToggleButton(wrapper).simulate('click'); - - expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(true); - }); - - it('closes the popover', () => { - wrapper.prop('closePopover')(); - - expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); - }); - }); - - describe('toggle button props', () => { - it('defaults to a size of xs and flush', () => { - const wrapper = shallow(); - const toggleButton = getToggleButton(wrapper); - - expect(toggleButton.prop('size')).toEqual('xs'); - expect(toggleButton.prop('flush')).toEqual('both'); - }); - - it('passes down custom button props', () => { - const wrapper = shallow(); - const toggleButton = getToggleButton(wrapper); - - expect(toggleButton.prop('size')).toEqual('l'); - }); - }); - - describe('links', () => { - const wrapper = shallowWithIntl(); - const links = wrapper.find(FormattedMessage).dive(); - - it('renders a documentation link', () => { - const docLink = links.find(EuiLink).first(); - - expect(docLink.prop('href')).toContain('/user-interfaces.html'); - }); - - it('renders a link back to the standalone UI', () => { - const switchLink = links.find(EuiLink).last(); - - expect(switchLink.prop('href')).toBe('http://localhost:3002'); - }); - }); -}); - -describe('appendBetaNotificationItem', () => { - const mockSideNav = { - name: 'Hello world', - items: [ - { id: '1', name: 'Link 1' }, - { id: '2', name: 'Link 2' }, - ], - }; - - it('inserts a beta notification into a side nav items array', () => { - appendBetaNotificationItem(mockSideNav); - - expect(mockSideNav).toEqual({ - name: 'Hello world', - items: [ - { id: '1', name: 'Link 1' }, - { id: '2', name: 'Link 2' }, - { - id: 'beta', - name: '', - className: 'betaNotificationSideNavItem', - renderItem: expect.any(Function), - }, - ], - }); - }); - - it('renders the BetaNotification component as a side nav item', () => { - const SideNavItem = (mockSideNav.items[2] as any).renderItem; - const wrapper = shallow(); - - expect(wrapper.hasClass('betaNotificationWrapper')).toBe(true); - expect(wrapper.find(BetaNotification)).toHaveLength(1); - }); - - it('does nothing if a sidenav with no items was passed', () => { - const mockEmptySideNav = { name: 'empty' }; - appendBetaNotificationItem(mockEmptySideNav); - - expect(mockEmptySideNav).toEqual({ name: 'empty' }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx deleted file mode 100644 index 14f2d54b55398..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; - -import { - EuiPopover, - EuiPopoverTitle, - EuiPopoverFooter, - EuiButtonEmpty, - EuiButtonEmptyProps, - EuiText, - EuiLink, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public'; - -import { docLinks } from '../doc_links'; -import { getEnterpriseSearchUrl } from '../enterprise_search_url'; - -import './beta.scss'; - -interface Props { - buttonProps?: EuiButtonEmptyProps; -} - -export const BetaNotification: React.FC = ({ buttonProps }) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const togglePopover = () => setIsPopoverOpen((isOpen) => !isOpen); - const closePopover = () => setIsPopoverOpen(false); - - return ( - - {i18n.translate('xpack.enterpriseSearch.beta.buttonLabel', { - defaultMessage: 'This is a beta user interface', - })} - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - anchorPosition="rightDown" - repositionOnScroll - > - - {i18n.translate('xpack.enterpriseSearch.beta.popover.title', { - defaultMessage: 'Enterprise Search in Kibana is a beta user interface', - })} - -
- -

- {i18n.translate('xpack.enterpriseSearch.beta.popover.description', { - defaultMessage: - 'The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.', - })} -

-
-
- - - Learn more - - ), - standaloneUILink: ( - switch to the Enterprise Search UI - ), - }} - /> - -
- ); -}; - -export const appendBetaNotificationItem = (sideNav: KibanaPageTemplateProps['solutionNav']) => { - if (sideNav?.items) { - sideNav.items.push({ - id: 'beta', - name: '', - className: 'betaNotificationSideNavItem', - renderItem: () => ( -
- -
- ), - }); - } -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index bc612de884f8b..5b02756e44b52 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -7,8 +7,6 @@ import { setMockValues } from '../../__mocks__/kea_logic'; -jest.mock('./beta', () => ({ appendBetaNotificationItem: jest.fn() })); // Mostly adding this to get tests passing. Should be removed once we're out of beta - import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index 5da455283eceb..affec11921545 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -23,8 +23,6 @@ import { HttpLogic } from '../http'; import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; import { Loading } from '../loading'; -import { appendBetaNotificationItem } from './beta'; - import './page_template.scss'; /* @@ -63,8 +61,6 @@ export const EnterpriseSearchPageTemplate: React.FC = ({ const hasCustomEmptyState = !!emptyState; const showCustomEmptyState = hasCustomEmptyState && isEmptyState; - appendBetaNotificationItem(solutionNav); - return ( Date: Mon, 28 Jun 2021 14:41:06 -0400 Subject: [PATCH 12/74] [Vega] Allow faceted Vega-Lite charts to take correct size (#103352) * [Vega] Allow faceted Vega-Lite charts to take correct size * Add unit test * Update autosize docs * Add warning when autosize=none Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/user/dashboard/vega-reference.asciidoc | 45 ++++++++------ .../public/data_model/vega_parser.test.js | 58 +++++++++++++++++++ .../public/data_model/vega_parser.ts | 36 +++++++++++- .../public/vega_view/vega_base_view.js | 11 ++-- 4 files changed, 123 insertions(+), 27 deletions(-) diff --git a/docs/user/dashboard/vega-reference.asciidoc b/docs/user/dashboard/vega-reference.asciidoc index 6829e129cd3b6..638a21dbe1cc7 100644 --- a/docs/user/dashboard/vega-reference.asciidoc +++ b/docs/user/dashboard/vega-reference.asciidoc @@ -23,16 +23,8 @@ Learn more about {kib} extension, additional *Vega* resources, and examples. ====== Automatic sizing Most users will want their Vega visualizations to take the full available space, so unlike -Vega examples, `width` and `height` are not required parameters in {kib}. To set the width -or height manually, set `autosize: none`. For example, to set the height to a specific pixel value: - -``` -autosize: none -width: container -height: 200 -``` - -The default {kib} settings which are inherited by your visualizations are: +Vega examples, `width` and `height` are not required parameters in {kib} because your +spec will be merged with the default {kib} settings in most cases: ``` autosize: { @@ -43,17 +35,36 @@ width: container height: container ``` -{kib} is able to merge your custom `autosize` settings with the defaults. The options `fit-x` -and `fit-y` are supported but not recommended over the default `fit` setting. +These default settings are *not* applied if: + +* <> +* Your spec is Vega-Lite and contains a facet, row, column, repeat, or concat operator. In these +cases, providing `width` and `height` will affect the child size. + +To set the width or height manually, set `autosize: none` and provide the exact pixel sizes, including +padding for the title, legend and axes. + +``` +autosize: none +width: 600 +height: 200 +padding: { + top: 20 + bottom: 20 + left: 55 + right: 150 +} +``` To learn more, read about -https://vega.github.io/vega/docs/specification/#autosize[autosize] -in the Vega documentation. +https://vega.github.io/vega/docs/specification/#autosize[Vega autosize] +and https://vega.github.io/vega-lite/docs/size.html[Vega-Lite autosize]. -WARNING: Autosize in Vega-Lite has https://vega.github.io/vega-lite/docs/size.html#limitations[several limitations] -that can result in a warning like `Autosize "fit" only works for single views and layered views.` -The recommended fix for this warning is to convert your spec to Vega using the <> +NOTE: Autosize in Vega-Lite has https://vega.github.io/vega-lite/docs/size.html#limitations[several limitations] +which can affect the height and width of your visualization, but these limitations do not exist in Vega. +If you need full control, convert your spec to Vega using the <> `VEGA_DEBUG.vega_spec` output. +To disable these warnings, you can <>. [float] [[vega-theme]] diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js index f33c2bfc27630..e1cff146a93e0 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js @@ -14,6 +14,29 @@ import { bypassExternalUrlCheck } from '../vega_view/vega_base_view'; jest.mock('../services'); describe(`VegaParser.parseAsync`, () => { + function check(spec, useResize, expectedSpec, warnCount) { + return async () => { + const searchApiStub = { + search: jest.fn(() => ({ toPromise: jest.fn(() => Promise.resolve({})) })), + resetSearchStats: jest.fn(), + }; + expectedSpec = expectedSpec || cloneDeep(spec); + const mockGetServiceSettings = async () => { + return { + getFileLayers: async () => [], + getUrlForRegionLayer: async (layer) => { + return layer.url; + }, + }; + }; + const vp = new VegaParser(spec, searchApiStub, 0, 0, mockGetServiceSettings); + await vp.parseAsync(); + expect(vp.warnings).toHaveLength(warnCount || 0); + expect(vp.useResize).toEqual(useResize); + expect(vp.vlspec).toEqual(expectedSpec); + }; + } + test(`should throw an error in case of $spec is not defined`, async () => { const vp = new VegaParser('{}'); @@ -23,6 +46,41 @@ describe(`VegaParser.parseAsync`, () => { vp.error.startsWith('Your specification requires a "$schema" field with a valid URL') ).toBeTruthy(); }); + + test( + `should apply autosize on layer spec`, + check( + { + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + layer: [{ mark: 'bar' }], + encoding: { x: { field: 'a' } }, + }, + true, + expect.objectContaining({ + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + layer: [{ mark: 'bar' }], + encoding: { x: { field: 'a' } }, + autosize: { type: 'fit', contains: 'padding' }, + width: 'container', + height: 'container', + }) + ) + ); + + test( + `should not apply autosize on faceted spec`, + check( + { + $schema: 'https://vega.github.io/schema/vega-lite/v4.json', + mark: 'circle', + encoding: { row: { field: 'a' } }, + }, + false, + expect.not.objectContaining({ + autosize: { type: 'fit', contains: 'padding' }, + }) + ) + ); }); describe(`VegaParser._setDefaultValue`, () => { diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts index cc453922ce180..95b3b573a6bfa 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.ts +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.ts @@ -14,7 +14,7 @@ import { euiPaletteColorBlind } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; import { i18n } from '@kbn/i18n'; -import { logger, Warn, version as vegaVersion } from 'vega'; +import { logger, Warn, None, version as vegaVersion } from 'vega'; import { compile, TopLevelSpec, version as vegaLiteVersion } from 'vega-lite'; import { EsQueryParser } from './es_query_parser'; import { Utils } from './utils'; @@ -149,14 +149,14 @@ The URL is an identifier only. Kibana and your browser will never access this UR if (this.useMap) { this.mapConfig = this._parseMapConfig(); this.useResize = false; - } else if (this.spec) { - this._compileWithAutosize(); } await this._resolveDataUrls(); if (this.isVegaLite) { this._compileVegaLite(); + } else { + this._compileWithAutosize(); } } @@ -238,6 +238,36 @@ The URL is an identifier only. Kibana and your browser will never access this UR * Convert VegaLite to Vega spec */ private _compileVegaLite() { + if (!this.useMap) { + // Compile without warnings to get the normalized spec, this simplifies the autosize detection + const normalized = compile(this.spec as TopLevelSpec, { logger: logger(None) }).normalized; + + // Vega-Lite allows autosize when there is a single mark or layered chart, but + // does not allow autosize for other specs. + if ('mark' in normalized || 'layer' in normalized) { + this._compileWithAutosize(); + } else { + this.useResize = false; + if ( + normalized.autosize && + typeof normalized.autosize !== 'string' && + normalized.autosize.type === 'none' + ) { + this._onWarning( + i18n.translate('visTypeVega.vegaParser.widthAndHeightParamsAreRequired', { + defaultMessage: + 'Nothing is rendered when {autoSizeParam} is set to {noneParam} while using faceted or repeated {vegaLiteParam} specs. To fix, remove {autoSizeParam} or use {vegaParam}.', + values: { + autoSizeParam: '"autosize"', + noneParam: '"none"', + vegaLiteParam: 'Vega-Lite', + vegaParam: 'Vega', + }, + }) + ); + } + } + } this.vlspec = this.spec; const vegaLogger = logger(Warn); // note: eslint has a false positive here vegaLogger.warn = this._onWarning.bind(this); diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index 54084c7476b6b..0cf3f16c3d20c 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -232,18 +232,15 @@ export class VegaBaseView { } resize() { - if (this._parser.useResize && this._view && this.updateVegaSize(this._view)) { + if (this._parser.useResize && this._view) { + this.updateVegaSize(this._view); return this._view.runAsync(); } } updateVegaSize(view) { - // For some reason the object is slightly scrollable without the extra padding. - // This might be due to https://github.com/jquery/jquery/issues/3808 - // Which is being fixed as part of jQuery 3.3.0 - const heightExtraPadding = 6; - const width = Math.max(0, this._$container.width()); - const height = Math.max(0, this._$container.height()) - heightExtraPadding; + const width = Math.floor(Math.max(0, this._$container.width())); + const height = Math.floor(Math.max(0, this._$container.height())); if (view.width() !== width || view.height() !== height) { view.width(width).height(height); From 043a2e2fe8d6feefad0d41764ead1614f2eb7ac6 Mon Sep 17 00:00:00 2001 From: Byron Hulcher Date: Mon, 28 Jun 2021 14:50:32 -0400 Subject: [PATCH 13/74] [App Search] New Precision Slider for Relevance Tuning (#103015) * Precision is now a required param for search settings * Made RelevanceTuningLogic aware of precision param * New PrecisionSlider component * Add PrecisionSlider to RelevanceTuning * Fix types in test * Fix imports for PrecisionSlider * Slight panel and text adjustments. * Comment out docs link * Add commented out test for docs * Can we just all agree not to talk about this commit * Restore docs link * Fix docs link again * Clean-up step description logic * Test for documentation link * Moving the spacer to align titles. * Missing test for updatePrecision * Fix CSS for step description * Remove containing EuiPanel * Improve screen reader experience Co-authored-by: Davey Holler --- .../components/precision_slider/constants.ts | 101 ++++++++++++++ .../components/precision_slider/index.ts | 8 ++ .../precision_slider/precision_slider.scss | 3 + .../precision_slider.test.tsx | 84 ++++++++++++ .../precision_slider/precision_slider.tsx | 124 ++++++++++++++++++ .../relevance_tuning.test.tsx | 2 + .../relevance_tuning/relevance_tuning.tsx | 8 +- .../relevance_tuning_form.tsx | 1 - .../relevance_tuning_logic.test.ts | 17 +++ .../relevance_tuning_logic.ts | 7 + .../components/relevance_tuning/types.ts | 2 +- .../components/relevance_tuning/utils.test.ts | 1 + 12 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts new file mode 100644 index 0000000000000..194fbfa6d11ac --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/constants.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +const STEP_01_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step01.description', + { + defaultMessage: 'Lowest precision and highest recall setting.', + } +); + +const STEP_02_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step02.description', + { + defaultMessage: 'Default. High recall, low precision.', + } +); + +const STEP_03_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step03.description', + { + defaultMessage: 'Increasing phrase matching: half the terms.', + } +); + +const STEP_04_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step04.description', + { + defaultMessage: 'Increasing phrase matching: three-quarters of the terms.', + } +); + +const STEP_05_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step05.description', + { + defaultMessage: 'Increasing phrase matching requirements: all but one of the terms.', + } +); + +const STEP_06_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step06.description', + { + defaultMessage: 'All terms must match.', + } +); + +const STEP_07_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step07.description', + { + defaultMessage: + 'The strictest phrase matching requirement: all terms must match, and in the same field.', + } +); + +const STEP_08_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step08.description', + { + defaultMessage: 'Decreasing typo tolerance: advanced typo tolerance is disabled.', + } +); + +const STEP_09_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step09.description', + { + defaultMessage: 'Decreasing term matching: prefixing is disabled.', + } +); + +const STEP_10_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step10.description', + { + defaultMessage: 'Decreasing typo-tolerance: no compound-word correction.', + } +); + +const STEP_11_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.step11.description', + { + defaultMessage: 'Exact spelling matches only.', + } +); + +export const STEP_DESCRIPTIONS = [ + undefined, // The precision number we get from the API starts with 1 instead of 0, so we leave this blank + STEP_01_DESCRIPTION, + STEP_02_DESCRIPTION, + STEP_03_DESCRIPTION, + STEP_04_DESCRIPTION, + STEP_05_DESCRIPTION, + STEP_06_DESCRIPTION, + STEP_07_DESCRIPTION, + STEP_08_DESCRIPTION, + STEP_09_DESCRIPTION, + STEP_10_DESCRIPTION, + STEP_11_DESCRIPTION, +]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts new file mode 100644 index 0000000000000..898c50e811abe --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PrecisionSlider } from './precision_slider'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss new file mode 100644 index 0000000000000..1f6d558556444 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.scss @@ -0,0 +1,3 @@ +.stepDescription { + min-height: $euiSize * 4; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx new file mode 100644 index 0000000000000..d7d06333517ff --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { rerender } from '../../../../../test_helpers'; + +import { PrecisionSlider } from './precision_slider'; + +const MOCK_VALUES = { + // RelevanceTuningLogic + searchSettings: { + precision: 2, + }, +}; + +const MOCK_ACTIONS = { + // RelevanceTuningLogic + updatePrecision: jest.fn(), +}; + +describe('PrecisionSlider', () => { + let wrapper: ShallowWrapper; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(MOCK_VALUES); + setMockActions(MOCK_ACTIONS); + wrapper = shallow(); + }); + + describe('Range Slider', () => { + it('has the correct min and max', () => { + expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('min')).toEqual(1); + + expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('max')).toEqual(11); + }); + + it('displays the correct value', () => { + expect(wrapper.find('[data-test-subj="PrecisionRange"]').prop('value')).toEqual(2); + }); + + it('calls updatePrecision on change', () => { + wrapper + .find('[data-test-subj="PrecisionRange"]') + .simulate('change', { target: { value: 10 } }); + + expect(MOCK_ACTIONS.updatePrecision).toHaveBeenCalledWith(10); + }); + }); + + describe('Step Description', () => { + it('is visible when there is a step description', () => { + setMockValues({ ...MOCK_VALUES, precision: 10 }); + rerender(wrapper); + + expect(wrapper.find('[data-test-subj="StepDescription"]').render().text()).toEqual( + 'Default. High recall, low precision.' + ); + }); + + it('is hidden when there is no step description', () => { + setMockValues({ ...MOCK_VALUES, precision: 14 }); + rerender(wrapper); + + expect(wrapper.contains('[data-test-subj="StepDescription"]')).toBe(false); + }); + }); + + it('contains a documentation link', () => { + const documentationLink = wrapper.find('[data-test-subj="documentationLink"]'); + + expect(documentationLink.prop('href')).toContain('/precision-tuning.html'); + expect(documentationLink.prop('target')).toEqual('_blank'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx new file mode 100644 index 0000000000000..6fcfb28cb9e37 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiRange, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DOCS_PREFIX } from '../../../../routes'; +import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; + +import { STEP_DESCRIPTIONS } from './constants'; + +import './precision_slider.scss'; + +export const PrecisionSlider: React.FC = () => { + const { + searchSettings: { precision }, + } = useValues(RelevanceTuningLogic); + + const { updatePrecision } = useActions(RelevanceTuningLogic); + + const stepDescription = STEP_DESCRIPTIONS[precision]; + + return ( + <> + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.title', + { + defaultMessage: 'Precision tuning', + } + )} +

+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.description', + { + defaultMessage: 'Fine tune the precision vs. recall settings on your engine.', + } + )}{' '} + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link', + { + defaultMessage: 'Learn more.', + } + )} + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.recall.label', + { + defaultMessage: 'Recall', + } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.precision.label', + { + defaultMessage: 'Precision', + } + )} + + + + + { + updatePrecision(parseInt((e.target as HTMLInputElement).value, 10)); + }} + min={1} + max={11} + step={1} + showRange + showTicks + fullWidth + /> + {stepDescription && ( + <> + + + {stepDescription} + + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx index 48b536a954ed5..e903010518b10 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx @@ -16,6 +16,7 @@ import { shallow } from 'enzyme'; import { UnsavedChangesPrompt } from '../../../shared/unsaved_changes_prompt'; import { getPageHeaderActions } from '../../../test_helpers'; +import { PrecisionSlider } from './components/precision_slider'; import { RelevanceTuning } from './relevance_tuning'; import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; @@ -51,6 +52,7 @@ describe('RelevanceTuning', () => { it('renders', () => { const wrapper = subject(); expect(wrapper.find(RelevanceTuningCallouts).exists()).toBe(true); + expect(wrapper.find(PrecisionSlider).exists()).toBe(true); expect(wrapper.find(RelevanceTuningForm).exists()).toBe(true); expect(wrapper.find(RelevanceTuningPreview).exists()).toBe(true); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx index 2e87d6836199b..87fcf91718b57 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SAVE_BUTTON_LABEL } from '../../../shared/constants'; @@ -19,6 +19,7 @@ import { getEngineBreadcrumbs } from '../engine'; import { AppSearchPageTemplate } from '../layout'; import { EmptyState } from './components'; +import { PrecisionSlider } from './components/precision_slider'; import { RELEVANCE_TUNING_TITLE } from './constants'; import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; import { RelevanceTuningForm } from './relevance_tuning_form'; @@ -43,7 +44,7 @@ export const RelevanceTuning: React.FC = () => { pageTitle: RELEVANCE_TUNING_TITLE, description: i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description', - { defaultMessage: 'Set field weights and boosts.' } + { defaultMessage: 'Manage precision and relevance settings for your engine' } ), rightSideItems: engineHasSchemaFields ? [ @@ -74,6 +75,9 @@ export const RelevanceTuning: React.FC = () => { + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx index c35cd280c7a05..39200a699b3f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx @@ -42,7 +42,6 @@ export const RelevanceTuningForm: React.FC = () => { return (
-

{i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 8cb5b3b35d97c..147a3d38add19 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -32,6 +32,7 @@ describe('RelevanceTuningLogic', () => { ], }, search_fields: {}, + precision: 10, }; const schema = {}; const schemaConflicts = {}; @@ -60,6 +61,7 @@ describe('RelevanceTuningLogic', () => { searchSettings: { boosts: {}, search_fields: {}, + precision: 2, }, unsavedChanges: false, filterInputValue: '', @@ -225,6 +227,21 @@ describe('RelevanceTuningLogic', () => { }); }); }); + + describe('updatePrecision', () => { + it('should set precision inside search settings', () => { + mount(); + RelevanceTuningLogic.actions.updatePrecision(9); + + expect(RelevanceTuningLogic.values).toEqual({ + ...DEFAULT_VALUES, + searchSettings: { + ...DEFAULT_VALUES.searchSettings, + precision: 9, + }, + }); + }); + }); }); describe('listeners', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 00896c923616b..45f56c1ef36b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -88,6 +88,7 @@ interface RelevanceTuningActions { optionType: keyof Pick; value: string; }; + updatePrecision(precision: number): { precision: number }; updateSearchValue(query: string): string; } @@ -143,6 +144,7 @@ export const RelevanceTuningLogic = kea< optionType, value, }), + updatePrecision: (precision) => ({ precision }), updateSearchValue: (query) => query, }), reducers: () => ({ @@ -150,11 +152,16 @@ export const RelevanceTuningLogic = kea< { search_fields: {}, boosts: {}, + precision: 2, }, { onInitializeRelevanceTuning: (_, { searchSettings }) => searchSettings, setSearchSettings: (_, { searchSettings }) => searchSettings, setSearchSettingsResponse: (_, { searchSettings }) => searchSettings, + updatePrecision: (currentSearchSettings, { precision }) => ({ + ...currentSearchSettings, + precision, + }), }, ], schema: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index bd1bdf11bd9ec..e1e0ddcd95423 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -69,5 +69,5 @@ export interface SearchSettings { boosts: Record; search_fields: Record; result_fields?: object; - precision?: number; + precision: number; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts index 20380717a4074..8e13f075c52b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/utils.test.ts @@ -55,6 +55,7 @@ describe('removeBoostStateProps', () => { weight: 1, }, }, + precision: 10, }; expect(removeBoostStateProps(searchSettings)).toEqual({ ...searchSettings, From 73e8871be008f8b3159245183cca687b533adb59 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Mon, 28 Jun 2021 11:57:17 -0700 Subject: [PATCH 14/74] [Alerting][Docs] Support enablement documentation. (#101457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Alerting][Docs] Support enablement documentation. * additional docs * fixed links * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * fixed common issues * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * fixed due to comments * fixed TM health api page * fixed TM health api page 2 * Apply suggestions from code review Co-authored-by: ymao1 Co-authored-by: Mike Côté * Apply suggestions from code review Co-authored-by: Mike Côté Co-authored-by: ymao1 * fixed due to the comments * fixed due to the comments * fixed experimental flag * fixed due to the comments * Apply suggestions from code review Co-authored-by: ymao1 * Update docs/user/alerting/alerting-troubleshooting.asciidoc Co-authored-by: ymao1 * fixed due to the comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: ymao1 Co-authored-by: Mike Côté --- docs/api/task-manager/health.asciidoc | 8 +- .../alerting-troubleshooting.asciidoc | 366 ++++++++---------- .../images/connector-save-and-test.png | Bin 0 -> 150209 bytes .../alerting/images/email-connector-test.png | Bin 0 -> 150075 bytes .../alerting/images/index-threshold-chart.png | Bin 0 -> 201346 bytes .../alerting/images/rules-details-health.png | Bin 0 -> 278648 bytes .../images/rules-management-health.png | Bin 0 -> 193781 bytes .../alerting/images/teams-connector-test.png | Bin 0 -> 116709 bytes .../alerting-common-issues.asciidoc | 253 ++++++++++++ .../troubleshooting/event-log-index.asciidoc | 201 ++++++++++ .../testing-connectors.asciidoc | 72 ++++ docs/user/api.asciidoc | 1 + .../task-manager-troubleshooting.asciidoc | 70 ++++ 13 files changed, 753 insertions(+), 218 deletions(-) create mode 100644 docs/user/alerting/images/connector-save-and-test.png create mode 100644 docs/user/alerting/images/email-connector-test.png create mode 100644 docs/user/alerting/images/index-threshold-chart.png create mode 100644 docs/user/alerting/images/rules-details-health.png create mode 100644 docs/user/alerting/images/rules-management-health.png create mode 100644 docs/user/alerting/images/teams-connector-test.png create mode 100644 docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc create mode 100644 docs/user/alerting/troubleshooting/event-log-index.asciidoc create mode 100644 docs/user/alerting/troubleshooting/testing-connectors.asciidoc diff --git a/docs/api/task-manager/health.asciidoc b/docs/api/task-manager/health.asciidoc index 7418d44bbfd33..758e3fdab1ca4 100644 --- a/docs/api/task-manager/health.asciidoc +++ b/docs/api/task-manager/health.asciidoc @@ -1,5 +1,5 @@ [[task-manager-api-health]] -=== Get Task Manager health API +== Task Manager health API ++++ Get Task Manager health ++++ @@ -8,20 +8,20 @@ Retrieve the health status of the {kib} Task Manager. [float] [[task-manager-api-health-request]] -==== Request +=== Request `GET :/api/task_manager/_health` [float] [[task-manager-api-health-codes]] -==== Response code +=== Response code `200`:: Indicates a successful call. [float] [[task-manager-api-health-example]] -==== Example +=== Example Retrieve the health status of the {kib} Task Manager: diff --git a/docs/user/alerting/alerting-troubleshooting.asciidoc b/docs/user/alerting/alerting-troubleshooting.asciidoc index 08655508b3cba..e45a77d48da9c 100644 --- a/docs/user/alerting/alerting-troubleshooting.asciidoc +++ b/docs/user/alerting/alerting-troubleshooting.asciidoc @@ -1,263 +1,201 @@ [role="xpack"] [[alerting-troubleshooting]] -== Alerting Troubleshooting +== Troubleshooting ++++ Troubleshooting ++++ -This page describes how to resolve common problems you might encounter with Alerting. -If your problem isn’t described here, please review open issues in the following GitHub repositories: - -* https://github.com/elastic/kibana/issues[kibana] (https://github.com/elastic/kibana/issues?q=is%3Aopen+is%3Aissue+label%3AFeature%3AAlerting[Alerting issues]) - -Have a question? Contact us in the https://discuss.elastic.co/[discuss forum]. +The Alerting framework provides many options for diagnosing problems with Rules and Connectors. [float] -[[rule-cannot-decrypt-api-key]] -=== Rule cannot decrypt apiKey - -*Problem*: - -The rule fails to execute and has an `Unable to decrypt attribute "apiKey"` error. - -*Solution*: - -This error happens when the `xpack.encryptedSavedObjects.encryptionKey` value used to create the rule does not match the value used during rule execution. Depending on the scenario, there are different ways to solve this problem: +[[alerting-kibana-log]] +=== Check the {kib} log -[cols="2*<"] -|=== +Rules and connectors log to the Kibana logger with tags of [alerting] and [actions], respectively. Generally, the messages are warnings and errors. In some cases, the error might be a false positive, for example, when a connector is deleted and a rule is running. -| If the value in `xpack.encryptedSavedObjects.encryptionKey` was manually changed, and the previous encryption key is still known. -| Ensure any previous encryption key is included in the keys used for <>. - -| If another {kib} instance with a different encryption key connects to the cluster. -| The other {kib} instance might be trying to run the rule using a different encryption key than what the rule was created with. Ensure the encryption keys among all the {kib} instances are the same, and setting <> for previously used encryption keys. - -| If other scenarios don't apply. -| Generate a new API key for the rule by disabling then enabling the rule. - -|=== +[source, txt] +-------------------------------------------------- +server log [11:39:40.389] [error][alerting][alerting][plugins][plugins] Executing Alert "5b6237b0-c6f6-11eb-b0ff-a1a0cbcf29b6" has resulted in Error: Saved object [action/fdbc8610-c6f5-11eb-b0ff-a1a0cbcf29b6] not found +-------------------------------------------------- +Some of the resources, such as saved objects and API keys, may no longer be available or valid, yielding error messages about those missing resources. [float] -[[rules-small-check-interval-run-late]] -=== Rules with small check intervals run late +[[alerting-kibana-version]] +=== Use the debugging tools +The following debugging tools are available: -*Problem*: +* {kib} versions 7.10 and above +have a <> UI. -Rules with a small check interval, such as every two seconds, run later than scheduled. - -*Resolution*: - -Rules run as background tasks at a cadence defined by their *check interval*. -When a Rule *check interval* is smaller than the Task Manager <> the rule will run late. - -Either tweak the <> or increase the *check interval* of the rules in question. - -For more details, see <>. +* {kib} versions 7.11 and above +include improved Webhook error messages, +better overall debug logging for actions and connectors, +and Task Manager <>. [float] -[[scheduled-rules-run-late]] -=== Rules run late - -*Problem*: +[[alerting-managment-detail]] +=== Using rules and connectors list for the current state and finding issues +*Rules and Connectors* in *Stack Management* lists the rules and connectors available in the space you’re currently in. When you click a rule name, you are navigated to the <> for the rule, where you can see currently active alerts. +The start date on this page indicates when a rule is triggered, and for what alerts. In addition, the duration of the condition indicates how long the instance is active. +[role="screenshot"] +image::images/rule-details-alerts-inactive.png[Alerting management details] -Scheduled rules run at an inconsistent cadence, often running late. - -Actions run long after the status of a rule changes, sending a notification of the change too late. - -*Solution*: +[float] +[[alerting-index-threshold-chart]] +=== Preview the index threshold rule chart -Rules and actions run as background tasks by each {kib} instance at a default rate of ten tasks every three seconds. +When creating or editing an index threshold rule, you see a graph of the data the rule will operate against, from some date in the past until now, updated every 5 seconds. +[role="screenshot"] +image::images/index-threshold-chart.png[Index Threshold chart] -If many rules or actions are scheduled to run at the same time, pending tasks will queue in {es}. Each {kib} instance then polls for pending tasks at a rate of up to ten tasks at a time, at three second intervals. Because rules and actions are backed by tasks, it is possible for pending tasks in the queue to exceed this capacity and run late. +The end date is related to the rule interval (IIRC, 30 “intervals” worth of time). You can use this view to see if the rule is getting the data you expect, and visually compare to the threshold value (a horizontal line in the graph). If the graph does not contain any lines except for the threshold line, then the rule has an issue, for example, no data is available given the specified index and fields or there is a permission error. +Diagnosing these may be difficult - but there may be log messages for error conditions. -For details on diagnosing the underlying causes of such delays, see <>. +[float] +[[alerting-rest-api]] +=== Use the REST APIs -Alerting and action tasks are identified by their type. +There is a rich set of HTTP endpoints to introspect and manage rules and connectors. +One of the http endpoints available for actions is the POST <>. You can use this to “test” an action. For instance, if you have a server log action created, you can execute it via curling the endpoint: +[source, txt] +-------------------------------------------------- +curl -X POST -k \ + -H 'kbn-xsrf: foo' \ + -H 'content-type: application/json' \ + api/actions/connector/a692dc89-15b9-4a3c-9e47-9fb6872e49ce/_execute \ + -d '{"params":{"subject":"hallo","message":"hallo!","to":["me@example.com"]}}' +-------------------------------------------------- -* Alerting tasks always begin with `alerting:`. For example, the `alerting:.index-threshold` tasks back the <>. -* Action tasks always begin with `actions:`. For example, the `actions:.index` tasks back the <>. +experimental[] In addition, there is a command-line client that uses legacy Rules and Connectors APIs, which can be easier to use, but must be updated for the new APIs. +CLI tools to list, create, edit, and delete alerts (rules) and actions (connectors) are available in https://github.com/pmuellr/kbn-action[kbn-action], which you can install as follows: +[source, txt] +-------------------------------------------------- +npm install -g pmuellr/kbn-action +-------------------------------------------------- -When diagnosing issues related to Alerting, focus on the tasks that begin with `alerting:` and `actions:`. +The same REST POST _execute API command will be: +[source, txt] +-------------------------------------------------- +kbn-action execute a692dc89-15b9-4a3c-9e47-9fb6872e49ce ‘{"params":{"subject":"hallo","message":"hallo!","to":["me@example.com"]}}’ +-------------------------------------------------- -For more details on monitoring and diagnosing task execution in Task Manager, see <>. +The result of this http request (and printed to stdout by https://github.com/pmuellr/kbn-action[kbn-action]) will be data returned by the action execution, along with error messages if errors were encountered. [float] -[[connector-tls-settings]] -=== Connectors have TLS errors when executing actions +[[alerting-error-banners]] +=== Look for error banners -*Problem*: +The Rule Management and Rule Details pages contain an error banner, which helps to identify the errors for the rules: +[role="screenshot"] +image::images/rules-management-health.png[Rule management page with the errors banner] -When executing actions, a connector gets a TLS socket error when connecting to -the server. - -*Resolution*: - -Configuration options are available to specialize connections to TLS servers, -including ignoring server certificate validation, and providing certificate -authority data to verify servers using custom certificates. For more details, -see <>. +[role="screenshot"] +image::images/rules-details-health.png[Rule details page with the errors banner] [float] -[[rules-long-execution-time]] -=== Identify long-running rules +[[task-manager-diagnostics]] +=== Task Manager diagnostics -The following query can help you identify rules that are taking a long time to execute and might impact the overall health of your deployment. +Under the hood, Rules and Connectors uses a plugin called Task Manager, which handles the scheduling, execution, and error handling of the tasks. +This means that failure cases in Rules or Connectors will, at times, be revealed by the Task Manager mechanism, rather than the Rules mechanism. -[IMPORTANT] -============================================== -By default, only users with a `superuser` role can query the {kib} event log because it is a system index. To enable additional users to execute this query, assign `read` privileges to the `.kibana-event-log*` index. -============================================== +Task Manager provides a visible status which can be used to diagnose issues and is very well documented <> and <>. +Task Manager uses the `.kibana_task_manager` index, an internal index that contains all the saved objects that represent the tasks in the system. -Query for a list of rule ids, bucketed by their execution times: +[float] +==== Getting from a Rule to its Task +When a rule is created, a task is created, scheduled to run at the interval specified. For example, when a rule is created and configured to check every 5 minutes, then the underlying task will be expected to run every 5 minutes. In practice, after each time the rule runs, the task is scheduled to run again in 5 minutes, rather than being scheduled to run every 5 minutes indefinitely. -[source,console] +If you use the <> to fetch the underlying rule, you’ll get an object like so: +[source, txt] -------------------------------------------------- -GET /.kibana-event-log*/_search { - "size": 0, - "query": { - "bool": { - "filter": [ - { - "range": { - "@timestamp": { - "gte": "now-1d", <1> - "lte": "now" - } - } - }, - { - "term": { - "event.action": { - "value": "execute" - } - } - }, - { - "term": { - "event.provider": { - "value": "alerting" <2> - } - } - } - ] - } + "id": "0a037d60-6b62-11eb-9e0d-85d233e3ee35", + "notify_when": "onActionGroupChange", + "params": { + "aggType": "avg", }, - "runtime_mappings": { <3> - "event.duration_in_seconds": { - "type": "double", - "script": { - "source": "emit(doc['event.duration'].value / 1E9)" - } - } + "consumer": "alerts", + "rule_type_id": "test.rule.type", + "schedule": { + "interval": "1m" }, - "aggs": { - "ruleIdsByExecutionDuration": { - "histogram": { - "field": "event.duration_in_seconds", - "min_doc_count": 1, - "interval": 1 <4> - }, - "aggs": { - "ruleId": { - "nested": { - "path": "kibana.saved_objects" - }, - "aggs": { - "ruleId": { - "terms": { - "field": "kibana.saved_objects.id", - "size": 10 <5> - } - } - } - } - } - } + "actions": [], + "tags": [], + "name": "test rule", + "enabled": true, + "throttle": null, + "api_key_owner": "elastic", + "created_by": "elastic", + "updated_by": "elastic", + "mute_all": false, + "muted_alert_ids": [], + "updated_at": "2021-02-10T05:37:19.086Z", + "created_at": "2021-02-10T05:37:19.086Z", + "scheduled_task_id": "31563950-b14b-11eb-9a7c-9df284da9f99", + "execution_status": { + "last_execution_date": "2021-02-10T17:55:14.262Z", + "status": "ok" } } -------------------------------------------------- -// TEST - -<1> This queries for rules executed in the last day. Update the values of `lte` and `gte` to query over a different time range. -<2> Use `event.provider: actions` to query for long-running action executions. -<3> Execution durations are stored as nanoseconds. This adds a runtime field to convert that duration into seconds. -<4> This interval buckets the event.duration_in_seconds runtime field into 1 second intervals. Update this value to change the granularity of the buckets. If you are unable to use runtime fields, make sure this aggregation targets `event.duration` and use nanoseconds for the interval. -<5> This retrieves the top 10 rule ids for this duration interval. Update this value to retrieve more rule ids. -This query returns the following: - -[source,json] +The field you’re looking for is the one called `scheduled_task_id` which includes the _id of the Task Manager task, so if you then go to the Console and run the following query, you’ll get the underlying task. +[source, txt] -------------------------------------------------- +GET .kibana_task_manager/_doc/task:31563950-b14b-11eb-9a7c-9df284da9f99 { - "took" : 322, - "timed_out" : false, - "_shards" : { - "total" : 1, - "successful" : 1, - "skipped" : 0, - "failed" : 0 - }, - "hits" : { - "total" : { - "value" : 326, - "relation" : "eq" + "_index" : ".kibana_task_manager_8.0.0_001", + "_id" : "task:31563950-b14b-11eb-9a7c-9df284da9f99", + "_version" : 838, + "_seq_no" : 8791, + "_primary_term" : 1, + "found" : true, + "_source" : { + "migrationVersion" : { + "task" : "7.6.0" }, - "max_score" : null, - "hits" : [ ] - }, - "aggregations" : { - "ruleIdsByExecutionDuration" : { - "buckets" : [ - { - "key" : 0.0, <1> - "doc_count" : 320, - "ruleId" : { - "doc_count" : 320, - "ruleId" : { - "doc_count_error_upper_bound" : 0, - "sum_other_doc_count" : 0, - "buckets" : [ - { - "key" : "1923ada0-a8f3-11eb-a04b-13d723cdfdc5", - "doc_count" : 140 - }, - { - "key" : "15415ecf-cdb0-4fef-950a-f824bd277fe4", - "doc_count" : 130 - }, - { - "key" : "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2", - "doc_count" : 50 - } - ] - } - } - }, - { - "key" : 30.0, <2> - "doc_count" : 6, - "ruleId" : { - "doc_count" : 6, - "ruleId" : { - "doc_count_error_upper_bound" : 0, - "sum_other_doc_count" : 0, - "buckets" : [ - { - "key" : "41893910-6bca-11eb-9e0d-85d233e3ee35", - "doc_count" : 6 - } - ] - } - } - } - ] - } + "task" : { + "schedule" : { + "interval" : "5s" + }, + "taskType" : "alerting:.index-threshold", + "retryAt" : null, + "runAt" : "2021-05-10T05:18:02.704Z", + "scope" : [ + "alerting" + ], + "startedAt" : null, + "state" : """{"alertInstances":{},"previousStartedAt":"2021-05-10T05:17:45.671Z"}""", + "params" : """{"alertId":"30d856c0-b14b-11eb-9a7c-9df284da9f99","spaceId":"default"}""", + "ownerId" : null, + "scheduledAt" : "2021-05-10T04:50:07.333Z", + "attempts" : 0, + "status" : "idle" + }, + "references" : [ ], + "updated_at" : "2021-05-10T05:17:58.000Z", + "coreMigrationVersion" : "8.0.0", + "type" : "task" } } -------------------------------------------------- -<1> Most rule execution durations fall within the first bucket (0 - 1 seconds). -<2> A single rule with id `41893910-6bca-11eb-9e0d-85d233e3ee35` took between 30 and 31 seconds to execute. -Use the <> to retrieve additional information about rules that take a long time to execute. \ No newline at end of file +What you can see above is the task that backs the rule, and for the rule to work, this task must be in a healthy state. This information is available via <> or via verbose logs if debug logging is enabled. +When diagnosing the health state of the task, you will most likely be interested in the following fields: + +`status`:: This is the current status of the task. Is Task Manager is currently running? Is Task Manager idle, and you’re waiting for it to run? Or has Task Manager has tried to run it and failed? +`runAt`:: This is when the task is scheduled to run next. If this is in the past and the status is idle, Task Manager has fallen behind or isn’t running. If it’s in the past, but the status is running, then Task Manager has picked it up and is working on it, which is considered healthy. +`retryAt`:: Another time field, like runAt. If this field is populated, then Task Manager is currently running the task. If the task doesn’t complete (and isn't marked as failed), then Task Manager will give it another attempt at the time specified under retryAt. + +Investigating the underlying task can help you gauge whether the problem you’re seeing is rooted in the rule not running at all, whether it’s running and failing, or whether it is running, but exhibiting behavior that is different than what was expected (at which point you should focus on the rule itself, rather than the task). + +In addition to the above methods, broadly used the next approaches and common issues: + +* <> +* <> +* <> + +include::troubleshooting/alerting-common-issues.asciidoc[] +include::troubleshooting/event-log-index.asciidoc[] +include::troubleshooting/testing-connectors.asciidoc[] diff --git a/docs/user/alerting/images/connector-save-and-test.png b/docs/user/alerting/images/connector-save-and-test.png new file mode 100644 index 0000000000000000000000000000000000000000..35e5bcb21a5dcb2518b468b1adc0bb47dd9017d7 GIT binary patch literal 150209 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 YgNGbH9v1-bm!432r2Vk;!IRMc2eBvL0ssI2 literal 0 HcmV?d00001 diff --git a/docs/user/alerting/images/index-threshold-chart.png b/docs/user/alerting/images/index-threshold-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..f83fa9476eda72f8637d000c36c0226851c4ff80 GIT binary patch literal 201346 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


H4ge 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 KQ7Tig4E{fN!)dMn literal 0 HcmV?d00001 diff --git a/docs/user/alerting/images/rules-details-health.png b/docs/user/alerting/images/rules-details-health.png new file mode 100644 index 0000000000000000000000000000000000000000..ffdac4fcd19839519f698ac8e194c25d75847de7 GIT binary patch literal 278648 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�YR<*e1tp?|?CtmRW04wMMZ_a`%rS#1pv?qQ`6K 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 >fUOSc+^`XRDF9 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 literal 0 HcmV?d00001 diff --git a/docs/user/alerting/images/rules-management-health.png b/docs/user/alerting/images/rules-management-health.png new file mode 100644 index 0000000000000000000000000000000000000000..e81c4e07dd7b2e98e94508046e816fb312851e5d GIT binary patch literal 193781 zcmb5W1yoe++6GKBG?Ib>LrV^gbclqMf^-hu-HkLTDP4+$bPS!+4bojw0z=2p{NsDh z`Mz)c|9b!PGHcE3&E8M!=YHjM!di7TB)hpmzCwntX8&d=X*@#39bWPQMk}Mq+Ig3DmEJjPjI0leDm_$On z2*^y0LHGG7;fF|yL^X$fnn=^v8n1O-Tv1Kf!e{k}ybV~tzt}Ts4;@I44Jd0pX*ue3fo1B1+m5U$^=*RSUgb^P@!2k~>@ zefG%~v-tZvdQ##U*!qLoSqM!C0sc7n@;@EQl9eN4D1K-; zvCDARC4mlM#Z~t2wiq0FZfCk_mJuHIXdI0&yWS&kSbe1vKr^xp$02IKcxUx&s7Q9` z6In$Z%eva}g7^@L9IfQ!t)^F!u&}-{Xix)tBPubRzWFIm*$aF|>7u=$eWIhAb^Z_a zN_3laI7i_`#^2DLo`J_1lx!^F^6z^i%r}l?x(nCau_Sm_KfJBK`CMW8K@Ny6tKGEQw3_Zk_xvdE zxe4+UhcOE@v)r>7*)?uNqQEvF0b=)RbL{=QCoh7pm4md;Cs0k!V@NJW`%q(mBk1pd zc&#iq=5pvQ^g&^7X|G0_5PH(k)2*Q16zEntyyTk*AbIm2@lU0(>7@xeBckguD8p~o z$BxLb(}hNQe9?36jFdg3ENB-Qb*ZZf?G!!}4Yoy?@>qo3uBCnCZX0=87nGsz0iZ;f zqvjszQVhU4z_@cb0{))EvT8`HS8IUWD`o|e`KUR6qUcWipc4A_0~7Ab$fVo&1-Z#P zwX?nJ@5aN=OPJjqHgp~q%Xe9WeJAUZW+iQ?+z5msGkb82&I3Xy+Q}=NXQ_!PbzkA# z%=`?fyS(>sqK?@R^7r|qtVT~<##@$IM#XFVMVzNbyr#WA!DM11-o_Cz3b_r5hD4e9 zsp!YegWUtPfZg<*`*g2v=z}_Nh}*!W+Z_QQyT7+5borVT2exCcyvlcYodYRH4Dfpz zpo)d^#`CLoSTQBarTrIV_s^Uh=+i$C%{FkG1H}MnU_+vC)CeH4LVznt=Q%67>JMxV z($4{071$+cdp{T|P@xEI0WDuBpQFgMV>poLB53?ztM?Q9!Tl3wIY1vpLKZ*)2KI!b z8b!UNJpeqzkm`uFpre#~%@u1S75O5LtWP)>TPv{S6(8+XUwI$m#tSEM%U5+&&F{1z|@|)Dzo~ehoA~W6+j>C)WO%`*Wp;7E(ov>m!e6q zP(BN6;!I3=m7%1ouqeK$XhT!`gbLqVmM)&NKb1&4lKxl@HJ4yqdz^jz$@ps(^c@PV zC>sek#;}BmJ{2dQzwJ7@L<>i^?Z5H!N%jfXQ6iZsiOf!{wYad!(^DvSpCn>3pC;hCQ zNVDL#DF5x!NFkDvB@Oxl`l>MzGj=$>V#!?{W_?;6G7qU?soARrS&7Yj{XM(u;KJ~m zXy@}DbV8tfB2Rv-W@2;q#MOL9eV6iQOa8*Q-k;7BQB#96Iwf_ghtGG;eDFx5BpduqfN%q_)ZG8Q{eO&ZqBOKwGEL&wF;J8$dK{X2+r@a53U z8IK0XmSYOS#jbvnkNrdmpBQ=-8$-#l Q7Y74p?{!GvRUYKCnyyG&#mcOmwJv1~- zGh||BJsh3tn#w&KH0(U2kxIpPSZ%0@qAkm+uJydqV}Q%VQPZ;oQyr^#VSJKnT*b0s zu4<}kW?-4Qzi8H~Zi>RXh<8D7&@0gu;#&79WY{8|jE|R(-&GGg~t0 zN1kOYggyzq!cD*}CU+YS+3Ij)aw&9d;Li{c_VPGBx?UTVnf)mHtkS2e|4Fu9i|(mz zUE{LTg!{8ib?Ef(i}9s?y;c1kzqz1;CkLDh-`dwh3Lpi2&fm6OL7_7i%w*uCW9w$jua*`SPZ|-?1%* zIy&N+s=(H}W}VxQmkAgjgCj762$$(X>DU#U6E|XW6|9t|<&>1ml>E{O(yD0ke4*i$ z+`U()^k?Z!B6T1Ty}i4rfEevVzWUzHX&xJ| z#Fx;Q%C{uj=}PM|=()Li{yd>zIa4Pp^{8+<8)#H3QOhgVFVCvKc4;ub%IBTr9pK$3 z9wWxFww-3zxpB<&SV|m-9a!2^oB?S+xCJ?Mc7JMrX3d{!%e-*5#@Mes!!Kh~R$Nug zydt@%QolJiy-~{OZ}yy4o!D*M>ptJdDa5fw zm!`0=JF{PWxH(#>qpB9x^z1wOIo>(hd5Q<4U=kdkptIf;O!GaHIPKcf>FuFxTI9;l zJNKA8R%Pr^elFB6;JNYsM`2LGof6f{PEl3==^L9RfpX`Yhofs_|6+IRCC}_r98w<; zPwgigAs^1)zsBUhCM)S~>4|#NZ@pQV$}OHL&oi)YV{g9SkY4^o%8zA#v7F|d?$C4= zeUN$5lgRv*Y9Oq)MO8FjRNcqvcJfkS_jJGYwpEnOVB}B?)3D;JmKH+uBJLYbr=C66?4OZi${8{@0GF9zK8W1 zPK478cIpkk4&tZmXliZMFhhtXN4%sv+}m6FIRnF3epmSnfk#n&g;z~7*gYuwgWB)N zI0xKI=}Fkna;Mu`^df+8d)-|d0yTd`%A8?uEZpgcG}V+dQ&dD?hMxlvP!P!xP~m5Y z@UJN1v;R4lMtp&Q{MU6P1cWe41eAZbQGy@;eEy8|r_aBR$nl{F=zKC)vbVu;< zXD#1rx@amY2pZelvV%?RjZE1+Y#siLgCOi72tTzobpZoCY;EkE1wBM){%RozKmT(X zL<9V*iHo%eji%yT;46D4Qy?!p4?72qC?*gH6m~K(6I79s{&#oyUm`RXE-ns&AdtJe zJG(nKySV zmbP%3!RHX=;^F2K{;R|P=hpvD`LC{O&ZbVU>}}yaT}1zv>i_Qie{cT3JN`AN*8k4Q z$-)2f|IYb;ZvDHfFzCszUO`*j*OTyQAM0xL%2DHECN?x!&(GF+DFf|}NXz^PaB{M%rP37h`Kget-^D94m zXcyS_Zr%1C5jH>g{ZN0nvYo!)H}=_B@)ZIi3cmP1{CT;9TJ;1Vw6pR`9{~vf4ETpX zh8XyO^`L*+`cJ!C85BV9oy@YVKH4MZap*t>_eN7-d6McjO7`Ts2J2_LMT`9N<%t7H z0&TL;&7H~8hD#?-Kax?UFa=ip{tA-RBlBlTMf~TKQYsPGmKE=dq1|lw zBu`PC%Tdf?`gb>ksUAUAd{E%g*K!*p4IC^nhT4KY2cW_$eY>e29bs)5>&D)i(*Jx# z2uO@k0U+pRF@WTDC=jutx(FP2*RJteY~(GFl?^*d{mBp;U<>MeAmcAA{g3$zPEf}3 z0PfYz#X5s`hS}vtnuggT;|qLCffbYQaROgsexM*+N$JD;g%_TeNG9YbF~4|${Z-_G ztoEN1GCmFp~`%Q4A~r~nLYio2W7cq@LeMkIIh#M6{vXuouijg zaqBPhhla4X2tP6Y$KoZ95cu##yof0+F)g0Ce@ofQYWMi3hBgN)<=2W7WN=)E;dSi1 zQh(|zrw!R_yjJ>Vy2tSLI#Pc>lhcMp6>l8LN|lcED=;T&M{{tyk_dePU22gb&m#sy z2s}b!%2%0Pqm?N|JjM+1#0^FbYd>R(P9-qgvR7?YsC$nF!N0`%=MsZ|^a5X#Ng+9A zh#^p|!zIV2H#zyEXkmeP%2sPM#VH%QrSc9|(3K>(u^i7)yG#{2Ve{mls;ximgaOa5 zVc}{S^4ywkPluN0lg$T{mNFnQ9roYQIu<(&1v5i`K*hW=xXt4?hT>kiTRws{5RfQQ zqZt?n$G$O5TL=-6JO@l9=u3j1AcM)#Cg`d6tqi?#hsjVNuo}S>XA1ch*?Z}KsBr&U zVK`lES{k!9{Sa=@j^=i&Zz+f_U#$z8_0x7%T(h1dMd|CH%r$eERs^S;2rvXTy8F%bp{A~3oe{K;Oe)U!Qv?(D6uYug8i2W9$PP^PQ%9GWSC2F zOxq(k8WF{aaJKH@iyE=QD{$-2TmeqlG&UOjqm9MX1J=w;|NTq$$ z*vd%W7YC%?4YD}FmXIKDJ7L#T0G!1O6Hqh&5aysAg1D^=Zle4fI7Q|jM-dDQAge=z ztSbGGZrZ*_4aYXCWgv&M=yxei@emHs;A_DkLoY&+X-qIG)`s(Pg72dqI|iu<28A#O z3lsHO6=aJ;UNz|+6t5S;~H8Jexha*w#r8&I=q z1Bc20X6PN&%$inKksu;zAS*&T@*}7QBte94GCju%6;8-ihoRdMOZ}UarNgE$Yapv_ zgDeG+F7*&@uq8fpmJ{NVtBwvS4t@fN+H5gqeS`$#1M%?z69a|}&y(2F(Yk!{+Gx-_ z6h2j~(|43$g4?Is&y)}YIdJ?^#RNw%gLW!00==ph8LABHo0(B4Z6%D?&2XbyMGR6iyJ1UR#Vz2BK8zMe(y(V z(=1ds=%Q`NkNEg_xB#O4@KNjFLCHtt#0mRjXV^&#mA`^PO>=`%^LMSghT7B$fZ#dV z$C!ga7fckij_Ri=?4&!z1o^6h$uVaX1tFbK9c|+7=;w-@bLG`X$yqLx)=!7Zu=BSw zcvypCNP}r;_Xsv8)5_VK{Luw3 z*is2PTlszuQ;06fDk*$&46Y2NfkQ^CpvUmp5>iuY2O&PU@ppW)$|KUpupC5O8>oP6 zsEskCLB`5Nho3M1+6*EIZV%)rUSY#G0}<}%FptA*>;gIVCjw?URoO~p<0LuT0PK&k z+W7c$mBz^?+|(R60hT{me1HlyK40M~-*wN1;l&mu085ua1yqRyCPxrzeV?lipvB+n zGNF0|>X6MApA}D0$K*!jH0R~;Ucvp8w={+sOpjhdTF4?rDFmNm0wKk|P5Ms{cX5+0yno4 zxF6GscM=(GfVe>$##CKH&S2iFbGi9*!^S|6hZ+Rf=aQI^kBTxhhF0q-txS;~b;CcIyHl(DF z)n2^#Zaf?~X4kyff3PjH;e3G(PV5d>+&myy3;Q}M=x;3{0b{@^(?wH*$IvpK}{C0zA9*&zW;IMYXx zx-Q2&7UB5cwJd2W`wbVGq)PD=EoQE6ZDy4cdfJJkXddE@Xv&(ACje(B zNX#Qv??GCD1kPvHjLr}!jgmwH!+EjrsaYL1JVJn25sL{%>98?=luz1-l7rd-4M-93 zgpWo}Brm6wFPTz0^ccpK4GW+E$aBHDc6JaqU`IXO*G4(>Ks+V1wc65@!`oT|n-pD*TU<;f9hN0q!vbjEa46-xkpswrK~P9%tk7m1Q}>g%q!9}`Xl zcn}?-cf>oTyiW!}O~YEDrOwQCq9V$`p~9}mSP};$lqxA8G^G$fNiw8@@aV!>9tHoh z9PnJwMYh&arP0N*KVkdqVPJ66^&gQ_{3)Uk zev%0@94@tes2h>rcODU48hsm5@n8jnCM{_9PnY_m>UfHZo=>Xgz5V(@rNU5x$+d^f zY3w!WKnO1=_E&e5cNQ%RjQ=wpwJlOFrEz61V`I}J zX5PC_w0IHki)upXKhnlO1_69uF90)GLnmrH?ZR4mE(B?VK@nooC(lo%N>&In%=~< z-;L*G`=#2((Kvr8<{33S+W=oFO2;qyVd4L#li}G7KrsCg#Q;=XGo7zA*I;G)|2x%t zpv~zP+jy?lX)qh%4nxt`)Whx3qz|ZAjhXp)fvha}``e$zHIsbzJUIRvb_j>SZS%_@ z!Gco)z}~T;dw-Tl`YG33oqe0l;$>KgL7Rl14uoMl_Ko<#QeSNAs@A?j_6;7ois zJ96|u3yB0Nl#ciW)s5?C+I=yD8eM0Y1E1O1rbdeyLKMDO-&{~sUDvIcEH$2U4#!P( z=5e+>MSEBQ!1sV2|HA+D-D=2xv5-gpAaNjTAQc>cRfzGGz(cyZ70Q*SgX*5XM>H*~VKjki6Vn&9K74!f65T^!RvICZtD-{MwNiA*0=>nEV6vtMqGqZE3zU7a&=vA^&-`D&rcHOe%1YJP-O z?aj!`Z@C$6^Oj_3V#}n5%Pk9D)5AKoePw!0^aiayDkT>0n6#_IiI`NRc!cA!@^NKi zC^5Wum$UbA4CQ#txE_$e#X2&O#vlx%v&pvnZJTvcoR!(BRG zlM3JcvCOQ%nb3Z3UX_K@eBMt$!bvGuW5`Jil_-g3RB>Fb7@n?u|9?X!uz*eiu> z|7A~3-`exj?+*+%oDY1z+wijpD{ZFN+^_yy%ptIZEw&7vSMvE#CHe=1 z@^D<4`hArJLJ8&?&&$KJ11yL;GDyX4`@WqVGa6^=QQxdFfAJ{f<2fIcbJ9PzqIO1e zKl8lsB;&L3aB%-|T$P7a>JSIveiz8Wdw&KQ-Q2Tp{Y36Ews|J#Y6W7mYi!4S^)eeI za?^I-_-5Jn&{wO%5Znd)FjCr{iMcr_KbXYde?FtfQjnb`BvPjU|1$xY%b-*CQ1+L zmu?#rncu%S+~n+t)~Y;V^nAE`axKqwBpt z6QiUt|eyMpeM73w|DUySdjM6R}s9N7POo_Q9o_({;D{yF75uy6t+G zWwVGSUH%g}sZ{zrrre(OpoMq-Xu^19r(RZmf5~V)8cuPxKe!84GCI+cr`mgwb9nk0 z)cV=)4z1n~hU?Lgz38{V_QYU?kI;;g)2Q0#xO?*ZVS*oHGK(XA6=_)`^^$6R-{-Bu43d96SHF5lg9VzsgPs~N3>5;~^Z6aDNk zL)1i!8wsqvCLKgv-gd0sch};wI8|1<=~xJi>|9YVhJ!Xe;eR!KjOy_wtOv zclX^{3CiEg!!?m{+Y!Rp?+f026H9UrZ829>5_*OZ>nf6iaLcUBv9%Rd1K-7RI~qi($>A zaK!4UfxI)Ey*m~8Wv~P013B()-8rU+Nm(zKJw3bbv}@Quc}7ra(COJi<gvWy&3z8q=Zi{zOxZA**{m0CbqO9(+1-I``(iOwYQ>wG&b?4kuq(OT|$5EeJp|P zj3UJKd+R`sQbFeh3fpmFQO6F8kUY-pk4ent8g6c3OUg=0N%}FO)Y%`&NZk}z8LKg8 zgkG%-InaM`fKBdNvS}}GY4b8H@~&M^G0%?&>q}yjS24e@h^@9;U^hBXcNldX9A?|x zOx8csZLqqDrf|BUuzfAYUCD>j^YO|fx4Qo7B7CNJ_{22IICI&xneZ z7R3j961KN_>%1u6zxK;2CR^^$HdreJ7CW3;y4F^1ZI2B)#gAnfAcOUWAS-U(3*U)j z7PDlsO^15F(XiKS^WtAz<@S%?^o!cnMa;ug9$M91o4+H+;Qcfu;=TQj`14~e?*2kV zXmm3xJ+xWFtHXX|FY^W&{VaLGu4Qg+b7izdd^Y0pqxTi%PuSLKdk}*MtZ1dnmQfd| zP&{Rs*W$r*hx=UQ1i4Usf6+ss_xXPXfD&-9Nrv*##j_b1+{ikqWy&nwnCE%`iCE87 zFU3Ocxl2X@J8um^It&p(9LVU}rSh-9`pnd>Ep<&=zZ{&<4oJKT*%K~?XAF9LG5Xsc z?se>%cc$xu+o1*O#h?NoO1B6}5H(t<*BR6qWb$HYCSQ?dX9gy4Q`un$qk3tmHZ>?q z{MEXw;f>#euU zO&HoEb{{)!dk`s~$Z#XU6V7WlWWl6a@nvbV@tgo~W4z8Vw#h%SQ0+7~Gm$Slk|Cg1 za6|x5ZR4|DXwpBdIW6T!*BTs>epW8#zjWaJQ=5isUbB3g_v2KXfx&3|E4}-g!zhqv z=JG_nL*h6WExuch=(>KZd`)C}@@lpY&}Q;`Ozw0PaR<*BdJJap^q3`Sob7&@0{_eBRS_ zW%4(k7@WV)$*cM-(uKT3KeK4?{o>KSlP+GRYwU(d&K8%qup^_jk{rj?2+q25w?=#J zC~W60)Y?WhW*Bs166ab@^o$JN-h|;&aDkhijjQ<%ipix=G;!(N-(Gco#E&2WM9pkN zr!@2lO5bcxH^aS-kTt5WpS(n-2qcRbhYT|Y(I80qC|ZRT#>!G-n*pPI${Xz z#%HJ6O(S_Z@*E6Bis^y^c7wK-Qz_$!D7$ zpJeZ@;}af?+dL+l$1~Dp$E(fvDQrLNu#IInEd+0!fy*Qp)I%zjNqJQ0Z$7w^7|mv(gQqFvO`_I+lCK_qTk3R_ASy7(>U22G*7y@ z(KGYo*HJIKW$L1@H=gW+LQDnzSHGN(~ok>Lo93F7~;z zYqGh|xb4Ma*rNoDx7;tlleR(E$#2GTUMqw&)Vvcryf6vUrvSnOO(s-tvdy!T+wQLy z{DmhL#pXvV)P=C@`0MJKatW3r?mzaPHcgTNP6f-+{)B;Ot|7%M_SY-G6_75Sc00#B zL37Pe{8Xb0FV5c!RDzs1%Lhw*n+>SqeXnTKdrC`{((i@$iK;~zaWW(9i8Dng`Clv9 z@{MMVN`V&>Zdxr@0S{?q3`EmK?ON}#PopZ}!o~RJ4=Zf069XoI6A8*e(#L6r&m?(3Gz=FiNo(myzGyU-bV7ay@#?5+$nA8x5@jY6I1&uv>3!Vmz?We9GEVoy+n3=9c4Wek0<7% ze6rO~$Teb3--xrPE)TZ>$Yn5cz6#KItm1a`exAzaoA}||l{8*!lcV%kBIpf0eq7qs z@$VgUAa12RRz98%b$gZC7O&F>w#cgKjGUYzZ*K=dCqIz!y!CN8vjQtlttTVNZ(Vdm zN6)+IzeC_ofa(0qid3dvTg2(B}@V6R=6>~2=R(K`JfatxZ zvH)z+IoB`{>Ex#I`~>c?n9NqJjZwA>mP;t&*r3&EB7(^QS_ub#u6mMGhrtg^xvb>6Xf3NJiFRUvIZ;H7sjQv>W_RWgdv9}~sL46nGg(tEo zPr|MZbt`N;Oo)-z26OrM9%mZ(U8%8KQ<7I>QWo@*n>DXrEFiTq@>)!OlNtSvyM{D+ z;hU!Cr`ClTZ&h(UlX!6yzCd_K7IWV8-az4W5R@=q1B!pc08OSf$Gy5aY%`rITG==l zp~&?({x!XKuF|jAX_&yM__-qlE99x=<F^FqdDCI#oD)GlwJ}$1g-;h-)@-T1%ILv zd)Fv-S+SlNdSLa`CKy9tr!jwRJ~$$~|8B`USjhG~h6+|WS?cWg<7sImm*J{4qA#2o zi=yV%8T_sr7Tq6$F23$vz-4r~lyF|NYVjDrH6wy7El_I%$ZV@zRfk7QAA4kYq=TMq9z z)n@w(>a#a~H=7TU@Fs7Z5?9OJRn|syyq%5tP)6kWrI(h=ZZSCael=QlB~GW-Cb35m z6!SD~>dH^QVe&l>E52}z>+^DPP4&x8#^ehnd@ih(xj#;)8Jw58PcIsY zW+}09OrgxCUNO?%i5t1xEdk1q( z)EDUtn?57GO<+*wl$YCz^I`ay$e(>>%_Wm7ay{8~XyB zrnbrLFg{brU283E5M)i_IK8ZV5eE}{`owXl3&U8d*jgZc2ETJkLy!8f`7Y^nJixbf z+%*-W;MR;e^tBb9OPeyK=Svwn*%#a^bf;{V*Pw^VYbNUeC-sY4^QscSdpV#&k+YHGQ-I-q>5=d4tNm4nu>*s8IA zGryqR^AW`8}_@j7kK3qX^&$P_vu%FcF!{JtosN0YXqT1pI z#?0#}9s#ej@8|X!eh?mK$~$*fWqoV!#w)u4N+K7$)-2<{k4sm52nhXA=HZt`j0VZx zZde$VbB9;R1dCi9gD&2yb8HnapZ}taB@?u5snN}QkdDsGh0f&~9%QH#9a%c%VQjzW zlGhu@m(3NwX&38@cl9pO`OMR$p=9OQ+>LBOU~zjN1QPf z$N>rrYMQS}+|J%x-=T=CrOR31a)pDFWc1;9v$6U8x+NO-m8CK5Dwg@#5-2!57>tGG6EG)BiUUut78 zYM&B3jnn|zJOm=E zI*05DB4+-*Q)}WFxN1t18MB8SVmVbV$MCzx6xLSN2emG?O^O%(X2{+!@}2;aoQRDZ zGjT zU|T_xSg`cA#-~$%PxN|jY}D&WMz;9Fl#hvYz-SesqEt{sHQ`?gghL54v<|zbb2GA+ zU;Jk^oVn0D)E{c4(|BwtFKTu$^u|l!p*fo9O6B`MfMW3!G#^H-qZg^1yy09M5=Ano z$Y$s2_IhzYTJn{{_&3Mj8df+ z-<-|p@GrKh+M3j>oJIGlOvMIxXI}d$mp5!NdwTa7Zkzt%J*x1TGb(6n>DOjZN%p|Dvn9`<=k}XtPj|n2 z=EJ;N4|gNSbG6@*S#Dw4HKOE=EaiC$oEG>T?YL0&!mH)&RSKM_JzMTP{zLXPip6)0UGgI(oMQ(c`8cw=& zNd}>yykQL5Fgz>EqL{`L-{{I7y+;ri=#D8O=mt&slU2K|a7ks=KM5+)u6Z+Ez5*0- zgJM%aD;ZnhuH)b#>WX^y{w;Z+yb4;3BUUh*L8VGr$7OvE0A7 znJ1OuA5>3K!5Kwqi~i%US{K7E^p6w$uflNUx(DKr)xC;Wv$JLE%I*Eirb~6GEW_*B z;&$L!w}ql?8g^34m1@U?ttZKh!?M7Vo4cjlZCVkiPRNqg6mO{labW{$M_Eg2$ZwUH z)TykU(_9y*L-K?FLGpPPaWwbN(7&%NaNq*ia8iTxm{ro%kteov zcgJ>L-p|bCooEJ@YZGH$vv8I!H?L?{y+bxG=BIMNg&m@g7R$?Ifjf+kEk?vO{Zy%o z+@m0O6L#EF-_BCiQ_XK}tydw5Y-u{8JMVOyvdKj?R!%kigmJCA6FJ!D#T3 z#+ZJ*w!^p#fvSGgO-vK5x`$qLTKuFR?jrEmwX(=n{rTXPESSSLf)QSMGEWc_;eq9^ zTl-=oc~L+4)7iDKwrPPk%nn?<{_U>h<$4?yA$g(9> zWR6S}xeEQsImoJN6W{Q>-LZii+;sj@XMF;0zT*_C-|kla>N!nTFjWmK=3IY<~2+{~yht z9}wE+649$|hS3aboUXRpuiY@VR;4f5#!2iHO=A}lFqs!D9#3(Ob?)y^M`y1_(w<4* zq~DKCaklZg$b`Fc0~-nNmX@MyShXQKlpg*eYI#Gg%5^LQ^4o)hb73~hl7S^803i&F z2YbrYKgAonQfM7K0VnXt=bOr$*CH(+5(Mz`1M?ZZ#P)Ywd#ry>f*AWfr;1S(3KDa& zJP%O+WV^)urSV)E2Dh{QrQx1w!dM|v*$l!0mhVlk2*7NGt{AN&U*Spx9>O2?Q<$-> zrdJOFHQq(na(Ry|uG&2~!ndq$ar({Hu+D1sg6;PGP2-#LEWD=7Hx?`9ZDmzSn^7)= zr5_5iamt6~YjWafL*)roKlRN4g0FbqWU4Lq#~;B1Z06fdO#`F`e#FPEggyR7YM&!t zzQAQ%|DUZ`h0%~m`i`EpM431JVBHq@T@$EMX|xd;dECtC{P+23{NjMn`dtyu!A6^U zj37M|Nw6CvIc{=Y!pyU~$Bao9WSd_|H0@hoektw{Zqq-%G=5*2H_daZZ}GxYd2r2i zAlw+9#c_!b3R5kyvhc>Etl65RM>7|j5m&;3xNL;lKJcjpZ~?E!vje76;%quBfYWk!KO=EO8c--uaHe( zGlf;JPG+lYo*GNhgT4Ft46nR(ZFBDD&zw_}G&Leyspje1T9x4PlErFe6~(0063K%8 zDbmE)o-NTih&fh+@TIHh%IwH}HyU39l4RJBFE==5-uff!DX}YqzEed56q5 z%ucl&t*!D8yOp{=q4;ya+4o?@x7VD}#?LVyW-wCvi%w{{Q zi{5b6O8*Jzh@nNmt2~WCH*4NZ>Ln=!8sfkq!UX@HpXCViq(5QZ_FhDXcDQ&}BO)6> zntoN4s_jXVU9CCvr}NwEZaw4OrQJE#Jggrgwu^^LjN6}73_N~}2^*KZ{uWaGiwv+g z^I+;}d=wjN?%+0Asw2}AbJsVQSmvvTId$u8uXf&V=60r>yG74RXnkIj)k2v(M&(Pe zQ+slM>ISzCp5#9v3lGLXR&LRCc$|6X1~ye)o;UMNxr>*lWa^B; zSM=RTnQs4mc*X0#eV-60k1@N(ribaY_=*%=cp=e@tbf={pnvtP#*Z%?=x!+`pS4Jw zinJ)l=b+5+ca?`SX3!7em|()XP4_xkSa8?;fd@}dNAAEzciz85XlrKm;*Zt z^zNDI`%2r=V`eZLh`AQll>2|;LS*p8t5)NY$U`qHe6h5wXGeklz`M26*9I!gJe@XU{Ypn&dCRA)^B)?i zdp3enpOqpQ9EqG=-WM~6hxS;A{;7VtrGo>>(o4q(I(kz)kH|w(_IO;1LyX_fLiG?^ z>oNMS=4uak%J=ti_{V4b)W2+#+4P>Ow6M0ct=?~??~IrE7KFp1rsJ7y{`0>8VEvY= zjPf;B+o72kF^cIXUob*GWgl72a#m*5Uu}_`%(H-cy|nr+^W>BJYi8;IJFxk$Z9hj7 zSu`eN#no|w|0LoMEL_dIlAl=<7h4RTbaUfhY))E^7dj`0kCrovWxiCmnrVEoTkn9} zdcUR9Q0%+yP*8fmoBw86BVRjf3I0+u?Y4TkB~_!{U^0l&*-X)0WP7@BkZ#v0B5oe; z;BWkzuR#jM2b?-e6-N>Wkc0)Xlv}VL;>u&Oir(lNc-~~X(~kIXv5}>!iLUS5!MJ`s zTXQ=35g0Um0~@Jue+^q7dy~XUy)v53Cg5ap>%T@6vkk`md&f5e>B1>aFD*HNeRx9M zsFKCA0ku10CTwA5z<7VIKHpd-eO2pWy&d8( zW7US%DC${}q!uzrNqQ?dNGzr|{XlUK;}#4P4cgzJ$FJ=}>2RVbzvr9u*;?kx76I)i zx=rQHdA1r!!EuYdi?9JXCEn0$tl&?VBf;YguW|F{MqkbjJ$v5ejMnico#Pjk5bj*3 zUftoc>MJXu4=!av_sW78@VdB97k?6oo#cc7c}&m9q`F0FERx(j!&hj}4^#dh_TDq9 z$#z}aeH8@}DX$19f`|wxRVgAJM4B{3igf86loncuh=2-GM0!)I)F7SE6qF7T0tt|W zA|-?nLJJW>;Cq;BuD$18-*ArgcaJfDXF$qxx9hyl^SEWVFB=U?17%Q%4RUa>4j2#1 z#UjZPt-%&`CG!~$tMF_|sEpUcS%mD-f#U-gi6=J@u0tQ>{c-((Dz9wAvl;zE`KOS7|4k$OpJy7jeG5!0jx9HI}F>O^&;x0akM84vKr5y_UoSPWtO zo_f>9av4sQ7#rNq*E29kts5}e0V=BR(rk?(+ni-K!s5g0L8`<-x73=1Tv*>Yv?Mxv zW);MAGtZ_WbbPKaA98S|VLAi6&|{D_=<9jyh~aBI9&t~E1z0*6SGPXH^gW7pcay#) z8}m(sFlO`Dt2s zj{V7d-_&nY+tR(8=hAK3AEaZ#;9`$6M>zorlSJ8I5M3g0pewCB+3?%8fd`4V!Ao~S zHfZ&;pDx@Pl--pXypv}x)aiAb1EG@Fdh?@Hs)XF#!{o249Ws^TI8)^ms&gb(L{^Re%GM7^>Y!%Aq}GRK1T$qZy^f1 zP*CSSxB7`7qC4!jw-i?Av?#nBIQ>bUw-gTXY?r2`uVFyH4d@_UHX`t}cWf9|w zd+LB(FwS?x^0}sD&?#a^&2ltu4h4LSyX*4=kq05$d0vx;iDiC`T8*Ub)0Ca?SRv=4 z*0La=YyQUlr}x;eDyiS1c0cu^JX83pJagg69}M2|*o|#Mm5&CN;3IYWWJxfxTIjDb z`-&VF3|EOqPRs@3&m8)yl8gbb>BHND@#|FFXRnzMoppr{OxZ50w7O{6&0&X}=T5V&##KCWgG)wE9952n#>psYcU$4i;2k>}S>0;l~G zl9Uv*Z>u2Y!eNADr&yvZpimvsjF)o0zN$U4Par8BS@X42*r4au^;Kseb1y3Q0~?Mz zFSX%&57^IVi&`}uF73(hD@|Ad1~F1d_=Xzu-yTmTEfJKKLFL@yd(rjdt=n0Ns+YpX z=(_-4L9UG3_|Tr8H3h3u=tE~-WhrhT**Zr2&?%V*WVOciCUtw?U6!_#X!%ZIZMzcY zjc)W!=a!{A5*Q{GJT*Aia(UDG99b8 zv@9y&;BSyBsw0Cp1w*#0K8;i-(Vm%7IXQ+Tn>b+6?zdm2~Z8oIuocGSOsXEgF z56892+KlZCh)Sd*44G(lIV-<|fiOjvs}i6wewzWjl&P|wLq|Z@8NQ68xmvU>#(q|% zjL1v0R+fd|3eTvE`!|WM+E*{y#hjjTNGjgo#PAJjpiO&TM%e{*qW#`~8S+guFuJOr z{*q^xNbh-;`ET*{*~w`mKsSF!9om$%`h22zlNFWrQs~067d+8U&=Kdm3wqZ~y4Uj6 zWNNJBQ-KS3d0>Q2yV8;QgTY$J^RGOX#cTT!OqD-VSQHQ@7pYr}c7NTxdxIlUZBjf? zPuG_nS(_cxm8Bw$I#Ot`OYdx}_n0PK1!d!W9c*KH-@}GqeY*k(`K@H`%@ns4y=?l* z5@idcE!>EEeq$p{=2v?{v558zC^^UXr0eucyHYa|YR{0p`OetwFax%V(Z~J=@MjTI z2U1&yUrh^phNzEiGioejj8$ zc{H9~^?J!DU?%nHzH2KvRdb7w@80QXz4;*gEu%c?xzswa&CIo?C97qqnEiK+MrKsa zzH36}9l*e`*f;uj?+?J|=&}}!-+TyvO&L!)HbK5wP)!cs zJ9#u3AMojrXZ3v+6PI{`%#Z@kEOH~Sn$o}6Z+E}VtU}%0^s}4y_)6mO!TUZBXLZ)) z$Xk5bT-}>to z`ma_%UNS_|J2BW-R%d@yJHKy`&i0)`U~@kDGndpKkMMs*o?^;DIl=;CLnLL+eZF_0 zS~abA)LDQ4RI6F$xTb6@w0HANd66*Noq6i;*3hKsu0a1-diHCx4`Xvby`d$vnT?Rg z@Hd2v!4!xIXXE8Rb_~FCP!?qG8`H$&rW;-YQBAkvI$sd2zIut_RKcR_4FAa}y{f18j2ge+d zDeVh9Ej=bz{XIvXjbYPMQ^KG-(b+G7$<5KX*0LYJXDTFtap3i6_9+Pw9|3~M%REr~ z=Er_cpdbtCH}y(rC@Mwz?9x_Zoc7fr_n^Sx^IHxCf7-Sp$R{i?c~5Nrqz(6nY{&9f zQ^S|e_*<=;^n&Zcvz2z{A5hO{j^d0V4Kfhi(L4(vM0VgWF~6#^p|;9+goQb2s?m;cW;D z&)pjg!^X)B`hfy8|BX3Szm~EX<8~N{Pl*bfBc>QB3v|yO-D*~xYu;N*?+3iREGuzK zSErTh$3~&5lId$x8l4-tl$NDNGb!JhlgAwr;NXOh3bjC*XjsQ_jifv_WYlj`v|9m8 z>Bakd$=zuRDl?HafQeIg!XI(C(6y)kjk@1*S>B=fg1GVZ-zI&>_ySDWEZw8@i{nh) z;<|g&ALR<@@9p^SE{ti~i^}5_hC&Fo)HOOD(SXEP$wb%l?<^7RnBy3wWuDU+fj?txV6pgW1)nojA6fANkqbX&ReK z?xY56fU&SXAbYcEhifdPbaaNXHa$M<_?xdy*dTlVkW2J%WI=KCq`ktB-b8cd#wDZs zSM`sWXY27svz=DUU`01t*fLda?Q6s~Y3duDaf_dG%YJ#^iB3tt5R*%KbESmrzN7(?YB6WOicc{v=_#>l4O9Lprpg)`@7xP(?ZX+SwmE?UC{> zw*7ihkdq8sBUX&7%yepPSG|d;*d@fyRK68H6X9|4nNjxD9BfX$hU?YJY(`Yz9sbt2 zMi;^f_sy+)5Kyy+%i)P5Va4MXv6d?k#ZD}l@UK6qUw>yUm)~z9bzT(Yp7B7#bTlhQ zEvAF4)wkuv{WkQ=?BLeS25W(VeQqKM5Mw!bpo6YK(vcZPs@WGd11o7nS4f64a!uY$ zvsi~KxP_AbxKy`v#O9iTKod5!?i|lg)n6Gp!mX+s55GTfIyTq|#*Gaqz=p{SiGxe4 zp1uK!96!@j>FfbBpAfWTH}vZ^jMv(K*mP^(lWuAJu@SR;`>dE1>JviwQPlHLd%*0e zfV0w=N#oiNrHZ-zMR?Q8mzpN-^8wT1Pgpu(P*LK3BjDrnso$|ARlk$;%{*<=I|eO) zEnH*7?QSjd2l?K9lwv?xKUW?>0AMD4E3I7kR^l-eN3-=)#;t{eCfVxrpRRy_g+6}_ z=*m=59D5UT4I7ha|5b&rzX=PgNk5y!s=D(+>GxOKt{`%EQ#kKEt9JZZpvJ$by6#wJ z)e9~g1N@|D%T-glrsU~=R!h~#ZOJs+M*9U(vmtV5t29t|u>XDLu~k*$r7}FIe_%=V z{<(qBvcOkU3`O>%?VwGDilScf zxMvk^96lFkXfE3e7f84swA%xrSo*7Y8BdL37>xe3~U-@^j0G@2y=mjP^{ z?*+73P@e#oX9_phe#f{C!(9b3S68AEMq@;|wA@L~K4HQ|x8{=uK#O_5VzLPcXOg{= z)mC-|(N}!h<`pW=8v;QYUT9xa#l)k1GT+nF-uDa@6ao^y`L->SHZ800WOb+x(2?G- z!(m2$YQ-i{`rEB^GhkE zYtM^2uiSXatD+pS%d2=C0oz*GTDm>_cu-7T@%ceUP3q!*M+Dlc2p#m}^en;s;gY9g zx@0O#Kef9V(8UAV330z2AQTypqFA*D*xLon|3Z++>Ou#>n*NmhIN;EuM`#3T(sIJ( zwmr*CzBo(|56|qAFE5VKcN}r(eq~Ijqvf^XdkAzQmJ&cG?Mi5~`FMU4V07%yu|U|k zkOM=?&IJRCr@@aFS3z?LyB2hzb|SrrQk}5sX#OUUZQE(E0S_}TQ=dlDq->gCr|Qg> zn9OzkeiEXViqq|$wG84-II-0tZ9ogoIA zbV*1F@n4-wSRSaaK2Qn}cRX!#XMGA?eBY<5Pz^jx#cq%StK31QVZhMO@W0eO2p4q` zM}20Uv)1|&kc@B|SeUP46zGsntq`jEl83f%9+L0Pj5bP<%s-YRqbKb~M+n>Q7uV)l%?1F8 zF&2zbYVp&#Ci?1hwm5$F3Bc;Zl0%R{0pCF{wv&%w%ZSkWTO+~Q`+C#lZYL~ta=0geums@LQ*{Px=RBRZ7M9rRXc**dvSAi9tSB_T)KKfddZHZZRFB>ugVT7uzX6;Y1b5x#n zwA6L}MqwJJQi8?>CCM>9K+q-W27`chLYg)oMC#WK<|#MRN<9f((a5s-!Y_`!gWF)L zg`R^}&fM?*N`U*Jy>tGeUxy)cplP{@u35D;e(Gz7%HKc*8urg94-xG&jTGNmXltQg zs^?BgFHuc=$1N*!0^#ojG&Hw}JC`0sfbp`cbjrULa3{`W%>ci7{Dorpk@9r3Kv0w39aR3gf5A3mihyf2*xgoRPJN;*&!3|3bHR#VbYR2$JVe!n{wR_|Jm ztiW~q0Y?8q<^`ocdjK;fp2nsFg=cqk!IGr+{7Vz0?zVZS5hNvX z*{hZ%S6vv#t>_Iisoq zA^x}|kK%0$)agz`R}QhG?=HgsSgdnBi!6e+mKFvLSHIBzUNyfdW}ttu#^DX3xUK9w zg^~wZ+CQ8nxL5pWy*K&55jkLg2R{lkZhYcH4LX6I>Re0mAxTgu;3EeC!uDt0Q(XsQ zw@H9OI&+A6dh_gY0<*U~fy4WzI8Bi9{s+@_-#n#i)-t_N{FrU#==s0TAW#tdDuujz zta$LdRD-K-%kl2~=>*BIB+`sPk!l{VCCz0QG)r$X@+R_Fr7kbreBqziV2-_62$2aE~nOxQ9Tc@#V>5zn}-OwR_RX#>;M1&K;vj(`+h_ZJI5JxjeVhM$j`lf_Ai~m^Gfmy@$cVc zWXb;QN;J*d^_^DJ$r?Hl-qV3^m2ndl4m)bhEdYk?SX zIUPodhJtVDJ>fh6Lc;=JGA^H6K4t#=ESK>Z!Lp<6VFWr<4OG0@qUrQ7 zcDsr;C*O4%mQYeOP(qBY^xT0;3Go!E7r#RvC41Ug&ygUMJZ|GwKmMaH?TDUNacS8} z$&EnNi>UJKKDUZ&Z^VVY8g#UAVfcxoFX_2M=UxFYddmjygKjP=o!M?3y(HoyN$p9^ z6r#x_nKSTSde}JblA8z|>?ZQlory&Vu#`L*BXdR81bhi;yi5W+W>Zx^~C`K&mHP#oez`Qha&y zxXIV;=B!kel)ke2+fjLGrkWBAdq-~?_(laf=nLLK=2?c<8;iW!c8d~YbQ>4-OvxH6UW=`D}pe3I!a{6SAOak`HfL|O? z8Rm#n?p|G1!_g<35KTk~LdQP|(W z1AM#mTJ4FLN&xUZmMyik|*sy{n{tJijeDV^~;X=7Sl;-Y&hq$VnY{p{?Qx1uY%dy{ET|2{nT9WS~ zk5lX7riB9>Du;I#smDg(Gv8xP*sN+i(sy`hDXQ_sI6xm*bF8yoj&qsPZ^b&=<{yr_ z)}yUl_J%*XTrzCMLQGHNCo3xhkA1?Rk^wBOWFQwivlYFB?nT~Xber;;Ajxfm3% zVB13C>V5ppt?6#z8GqdmeOTpEq8F8LQC?xJ<~2?w_wJo?Qa`}|(Ya0!@PrN>WhnE1i-GhPYa%Q|3s*d z&Gz_BaT!3;C4V!Bcgoif#79jIZhsSK}4!4yXMmRbxYQfG(kP zqr-yj^BB%tz2xbC(6k(8&!36I4sCM)6|kVNuwGQmuu`g>8Yn-=2 z3z*#gCLB=xNMJS{P_u!QNoGVBo&A3Jv81grWSq4i9L{~voR?_T*!*a{r8UK*%JRpy zER{k^m!oG*mYd~X-Q#YG%H>@0e8G*JuLC?fF*oM zR{q&lBA=ZymmXqcf(}bc0ir=STl{eeOJhl2VAk^)Ulx{*!zWc@Tz;FIEVPi7&97j9 zx$)(h#>sugSErwy;<+wd+V^>suV0bWhQrUsy@*TXTLGyb^M#xTv#+O@C2@IL!2&WZ zR)6PaR0K{@0C`b#@mNs;&WDF6;oTNDwCWK?tO+wcuU?4=Hz=cYXiTD3Ep1+%s{3W%hBzHp9s+}cXv>88^ zD6KCj#GI(<;hH|+*yV8A+2mpBt#&QZI6MaMI!HqXw6lat`~fqAPdPYdM+P9^e!Je3 zEpFFoKHa_-r5e6;M~k}dWny`yt+C*4g~5~~#JW>^J=EWQTwXy=R1~YfB<6}pLNrK9 zoMB9uaui9sYg7ICku|-MjcZ`;@fj=_cPQXTt%y5$(r{N=p^2OR@+k3MneCm=6|n}rR)WKvv`^vmO`Vaj z?*|MssTCe+H6`&E&LB>-4vnSCu|B!5+uk5_8!{oqLA7-^x7orS@`X`ObUxlYdVZks z{__o|#Cp(!;;IYx?&$6gRC}dfH{dS1A$?y<>kQFGr1(?PfRM?~wa|sVWbj} zHI7EVdfF!5w$XLZefE=o+fZCnG;uQ9AogOOH1JYWvryEtz#rLzA0~4QKto|Gigafw)|3I=JlfReVf={ZbQ{ z8q~y59UW);Ub5p!THiH?Mr@;j-c8E@IJ4BJ(~oo9_qI3;pSfkU(MA++cWZFJewgTD zklpu4n^%xqMTs`Fbvs>GmMzVVmvpeiCBHO2DXxiEnU!9teib+afqX%ee=#v}YT9Ut zhf-E0$g9X?>y}*^e{M;(Ai&wzS8&?Duv%JF=wP##Z>uNUw_jhFtql8F=sXKz8)`Wr z;@~b9Dw;X3tDYe=! zT%`5R({cE`lmVS;92ev&dw%BoXL0tpJS$s;AU8?V>XvEwBMN3)!z^nt-M5owsHI48 zAy(f_F0iRz-6j;gDG?wB=V`o9EO zRkxJXDk=K}427J}bW3pGU2EAwqDj|lUn?37XH69Qm$LY!`5zVj8&7o&quaxZ281bg z;9ch{A`H7!7$ms2&xLmSQIhL(SKrD(p{nZtpPmyQpR-&)mZ~$g(e3L4iuF(T zV##jzns1Z(IDgY`2GV=^PB%mVsc@obR1%uZl~eQHeQI#X3ifl}&o%1t;>(nT+lBX3 z8sfSXOC>;2!MnAoIceY5J;KY|ngilc&V{OME+E|JD*JZjm93;eLV9ezogn)R2Em?! z9E`s*Pb!2Z9$DKyU$u(!q1-MNl!lDfOi24y`D?rgl1q2#Ac$N@O)wqM)x`x9f3d#O z^py44Wyp5=TEDbj5ZciWn&sH_Xq%sB>PYSPg>o??GPg{qTYjTA!@`daJezjFE}0-D zmn$@{wfc=9glunrDyW_KE~4y6(1*!k=JgT1E$C%3$jI*RF{vzix(2F_`0idXQC@TL z+wwdqVJ9?uaWWAC_ST7e3Ttb;jylY$_3L&n-tm0$1!xX%=UyX=rk%;gQHh;;sp_QA z1ujg`^Ndcp+fnpqUO7K>)>bs}HgF+lP1FVs-r3tM|GuNDW>Z3opy z8#6f;8cZvFb2h#q#jOArgm~>9RlJ}oghRqw=h%WKsQ2O7$)|#g8IHS3AEGb}*b&!! zFuzq!Qf{LbkfmoC{c89H;31jl_HK0&J$0NR9K_uO8@^PU zzr5p};}0QDtISr%ly(5Juf=_E9uOLc>v+rAJPp=%1V)-B+ z1h#njdoL*UDC5Z1O8EQ2Rh@~e7SJd%L9VX{@cjX@aIH6*)#qyM({C>e?4NiCZZt0p zC~SQI^KUF9s`gJecw)oGIdNB70tt8IHk@ZKlQHn}^~3rfqQss@t=I{F2SADQ@dO>S zT?!LbEFYwc+;mm6i7r7~+FaMeHu{*ireKya4F1bX-`j;A$fL(`_oT4)(*$?|u9Pa|B*kn-0<~x7V@NhjT7Y?-&w>h zcpOcVQRZCQno6-$Cw)9p{#!K(bRh8~XAaHyBY}-MOYm_3uV>?-h!x~e2Eg^ZujA>a)_kgWTTtLR}(Kh>w0!ri^h*^We<`on@ldNeCrHkL?i0~ zxLvV9-`PLsMgN7?IQR6z28b^t5+UvoE7BW+Rqs(O5uEh`wOSth+#7$-fxX{@W+*vE zYeoPL)rh}4{#TSuwVpF>H`X$nko|pJ=3%{+*eRPpE&HaWn#ValyN3JF1}HSw?1!1x z{ts`)wFYOjQ}j0WLxO2yN=bUU$_ng_3>7YX@(e&^KWRTzDj|RV&)uwl`I0)1o+6Ao z6+hLG7DGy@y-p9jOSAhEOb2y09k;I6UEDtAJ6H$-oV!6ci`#!zVgKd1@OYh^c9?wc z+cfBD=apiBhNQn!r3rC!A__i1&-e03{74)TFweDkBEkca5r4_+#W8evE$IXR_>ubf zUjF|7>O=qIQS_YyzG&tnIFI1}>SO(5d^o)TGE?V1JF)!z#|iX*y&$Cl7};IMrM2Ju zv26OkUbu7>_{Hc%GBxi1QHJ`TFGLOj7fYmFA`dwxUv0b z?|Jc`z30TK{~Qhee{wWXOFOeR-Jqv(xcFFpuZVqV(vhjO;T_~~Pg`YgQTpb?tDQN) zzc$^g;-c%!o)VDWwH^~qek1a(x*g#mbwQ4myuhjOZr|yb;bvDBFEAXeLk~hz0Z1&= zZ`Z;V0Q$7B{H8fG{JS$)q#JO+E$AxQ|JFVx0xtrRi%zCg zaly#osZ+b@Uw@Yh4+149X1K{?)a1d38_UGEF)uxk9*bZ;6|dLU!S<&~VN!HQhg4|2 zTS&@EU}14!h0EA{39`TPWe6qtYqoE@XnW}FypK2lUzT#*zkk#E^PI|b=q`3hgH=_i z^Jp^`xOnk#=ZSBB z7X$gD^;<`$B-s}0q<*(MzH9ti)2lVGcoTtrVsA})V$b?y6UFVIE;z;^FD+I`6#*^&2GUH7(9#|lgt>Rw+~%2xJ&OUaF-)BdOp zM=udUGuuHcdFr&{0a^2q*-JVGb1&bThcb2SZz;)d^#T9r98bncQb+3(AL1w++oC5? zb|Xue^k}jIW#HphvFm&4-AOfm2*0G*j$1ZRtlN=9J}dTZ8kEW zThOLDx`(&$rTMMdm*j$8;_lc2qJ~BeX0N)Sg^VUF;FG^nIfw znu3<{cTH6rMDi|GK+T#fP^#k@DuD%yL%K}%7E8mS>|7V6g9_rgm1k_`mVJ1adW6EP zRt&<$hyGYRCAlB}5TAR9K)cF>TjrS&m#2%rDy`n8zO~G^gJ!65F^h44)$>Qwa-Qew`deAtU{C3J4Lmi&5s!7llBaD4X1%$YFXvdhx3s& zJAx@?O~LSi9ZHPsDgKRaR@H%*(UQ6>xTEUlmK|c2ACTW(K6B>9Gbzb>w8itvjoLg) zA0ZZp)LkC4yXpBq3$_2JK7+>**2XZc6*$!tiP6_0H zp(X~@&|d;F{&>3rn*mV#%4Ev)3bUV)&bHiL@@tYUwrLG&DJ-|M(p=~M3D-AVd2B`M z2Vhk9wzD!{0xC+aO&yrJJqtNX1uVJREXp$J$_lF+9bWuv(9Xj&V6jZ*ksl+d+_)hc zRe*I_9!S+=kzZt!7Qw@@t)RF=Ba~x$6h9lM_bhQi$K?rKmL)1=izjW!fq#2dg|-b} ze+6Giil!jaKf(ufVn3lMe2}#k?P3Ho3V2Imy%wlXg*c!`k_Fzpl_w(jiu_v5IQ*TC zgo4C`T1H+)D6w>*3_Z(5hN5+g*5pfa?8RAxYAYdmSF2rZ09Oi4u!U7w#)-@TwEYqv zLnV)tm7HR$BrT@yWvutwt)?#e{_ROQf90H?RlmvtMf0lI3NHIfVcLC&qh|SQWy>{4$ z#kDLnuCzj{zdf1J{}WPO$`p7OoD<<9k+-m^Q~t|?G*+cGqG_91SH#|udFD~qwY{y1 zB>;wrs|5r}kcD?@rOU8qu7q#(heDkZ)IeYta;fJD^Rky;UQyom zT(RHzJU=57c6EX{O}Nz!aGI0SZ6gdi_C1Qj5Bf_BBAbD>gg|M2P|-3D4p3`-$ie{%~b&pO%)Cy{w(mj;}vyV-kO>9))LyZx4gs%lX>M z6Dkpa!`#qsx1#*R&Br3^p%Wf62hm#-TO2;9oRYAg?1ndl8>%ZLWXsJ{1rn96t%e5h z<%PJEE4Bl0S>qr0o|SiUGiiAEaL};+i<*1UTZVFl={nu?f#=W0mQjJp)=J&^7ORIL zds0d`L~=x{tnLKDqk|)Bf{3jOC-PM>&f3bd7^X*oM5JEC;hyWBJC%i$GoHD&f4ldW z#M*&$z~q^|?`072_Au*Y zirNoc9Mh}U8y@d^wJZND*MG!~S6g(qZ6epdVv|f37%A^navlf(x};SWbtCQTI|mZx z)s@y(MD8++!mP^?Tas--#85m0cNMg=IU=w05i*-(u@=e`0D=IRh1Ww_%0|m0R&%x* z3UxlXf;fpJ4^wEGs6^*=r#&}2fUG6r+d)pS!r6fvx^fNRrJ~i%CRv!DeOU9yQ2E>! z^9mr41|`jd;m_Mg;PPyb`F{O33xKNM zQtcw!GK4>=`1!?TuVHT68V?2NZIf)$L!sgzYYTbwlr6Y4;=DZQF}SDH*ms%7RFZ1p zey|1)$n)K$O&!pWxgvlw(ju@M?2fRvgKVFvpLP#jARB*FiwN7c?+z7jTf&6lhV8D; zR@oAj1<2Nlgdyq0n9JNTO6YDgHrH3Qq1TkPCLA%slM4Wz*Ilbtp1?hC3xl=OqKJDg z8pWaOXu{9=0LUyo-o5(E)qJ|p_a>`apure^?Zt~(n+*A^l$TqMaHqq6^k zYK+WVXU&^J0A_BkP9I2MEMlq};=J!LpKut~C3xt*+#2IYe{SXSc`t->z|<4LRIVKt zF!#fYcQ;Oiz(eNwDlpQYJ1=*=z60M{5?yLkr0=N|u-RRYY-*j)&>>`Qt(}t-D@Mr7 zrS&qdD&P7&s#6DB*g1`lqOe{7t6!;_#JSdo^t=nXN6&gmWVtwz-pf#t_#tVkF#3sz zjcIq-bB@{fGPEh(wd-`9!nU~}lPQWwiGFyy)p86X9UWX##>+s$_| zErp~EfQ{u!>}SxVW^}8ZHb&jGh(26f>F{-!I2HE=`k1*Ee@Gn!7V3T*I!X*?b-el} zm>R&!)F`yYgZemMqs?~=g58r#*`U}#uuCc_oC@mg!At$g8cUchSM6N!QO}!W(kiK; zb?{L)6o?HjM?I_zobK?}b000&FkztO-r7lKSOii`33uU%s)yH`f=HJs)}H8+mK9~` z)c1WYZph2NGXeR%o9p=9UDifi_HBI}Mvm|kel_BF0@*pLD>3YJ_UK!TW-w(fo4-hZQBJj0YLp1b2yk8FA>`;|N*M$M{+4**@HK2W49L79NaQS0v?kX5K zYvC_zSx50Uzqye0y7?zWfb5KW(Yo&6>He3nv7X#?xn5#knC*QuVa$69qUlq@e0l$) zwaMnmZPS@hs`4f2Wp_#|`#790% zVnC~P*HTQ1om{-sd|LoRLp(3kN(5Ok?4cRs+y-q*RyMQ!0A#B!N;?ylsxDnS+Bv$5WlGhi4 z73SlMODU=<=8itoqp)R8cibuU=>~86j?jEqUdixu^r@Nv-s40G zUn6a9BHiNYR?+SEI!qxhhfXR)3CfV)YNhzC)xqG_hFiHal@^ZpY59XZA9Qh;vggIM z9vAQKb>V%lajvypuvR`}N%R0fm@mT!L-yTU$31__6djIhkw78aSFVgrXD6UM1G*4K z(G5=>?Mjo^n2|Ty`;+2%`Ycq#wk%o4^yYxY-Lv72_Sl4h7vP*tp zay9AW@xEsPpR#F<*j{JmW*aw~KIl{p=m6{^F@bXBRS@95apgt1SQYl2{c%qEhG;`X zR{r!Z0j;r$EOXCbKa2-SOpCsi^M%0|f++!ER0j060WU(CVqkNMOueIHMCI0_$bY2l zkJyC0!usx5j?M<{`m)N7a)y!tmvc25aoeaNx~8%@KLggJO3temKW7-W>+Tx>Lw#(z zM*lCp&;M`5brR4eIL|o#elP|AYX@OM2IywdiSz~Ew%vPu^gEi?MA*Cb^zi+c4lfWd zlY_8@He>j>$HY+5?jb04sPJ2Yj-;riB7c&XRx(?DmYn-!#uAI$w075f(N@<`h-z_vO6SKD=2O}qENJ%)uiDEV_$P^u&( zje5_lo5fDyn~?X?50;Di`TOVVBv56c2o2*!j zHm*0l;T_WVDnrKUmMii@joM4qwvtcTwuS`rk_jpA+io$4!15U51Hsg=NTL_Aw0EWw zPo4=Wp_n7crOFT(*&#nA`J})!_o64X(>Z8R-j3RQLIc5^Ka)<6mA<=pI;eFThCPQ0(gICUS@r94~_O8LdrY|J=6FXzPMHf0!dr*h_L8 z+rFPa9Mr7es)hL2#(1;AZRWlL$b+l#-POEJ*qGDmu!^>T&8)ie>>9OB{+ai1wJW9C z_O1_Z#17+H7R2E37WoGG&gS3~AG5TldD-$|DWL_9|Aom(# zJFP22qJppVww9vND6nEio-eFZJ%_4(*ugx8=;+ybf(T*xZS}}|Ie;HqtI00>I-;oB zatDK&9x@ z;KFy=i^PnJf4oICDGN|Y#xcJnoI4-B19sv(}&;FU+-@L( z*FE8Lb8P`Kf-7p1W5xM{2E4v^DnLiSj=GpcgJ*}oCh)52<%Sw4S%LYXlBEWhi1i(J zLWfRKD%MF)N~u5I3%GH=nSa2PTODvtFJJzwTfk;!rw;c;a3)( z`>>`xyN!e^vexy29FV=aD8VuEa{D(n$VFp~ly}+nTh+|q(Q^J9?3}fzrmu{N9$b(h z#%pZLB;>elCkunABus*uvxABYvYM&}o97OTPjkltm-cDWy|#hvUSRHVdoynQF-X2~+&ZERZCaJdIgVQ6(2moA>kNC;J2#A~;s7a15w!k+;32mgG26Q;Weveq z#Bzdd_m4J&Zur|k3LJ|_BiJ$22A@c0h?nOx^GOt!?m-gUT@to9@cZ3764?%0x)Civshc#swS zw$>9mZu({fzVSip=2$@$*bVipNQzGiLi+WD2-l<=!*3>jR%l;rlqM^^_o1kjsy!;a`5JU-nHCXlpmt$@A6T3UUQrrCHG{3 z-XR2?3VX!chqccR;_rKJ&I2@$Hy7X6(J=s0ZD<5OgtKaL{yIcdD`@gwI>i3apI{!5 zcpVF zuI-eu7b1`Dl!gs+qhxo29PLhZ4fWJ7RY`VQt~Z?Z=t1Yl*@kPb^E`tpKCr^I!a=;# zxDSl4rX;rmzdhmCr>cCUI5FRIldB#b@HQND5jx-!ALsG5khX1MPTdE8DGkisj;7 zfLXq`LJ7W#-hmVKI|SA<^!7g0(f8@>Ekaa*U8t@ATdpmtZXGp>91i|dyDD-Vuu2s# zbArkO=Bzrq46VcwUx6%n z%Rz-Mp%u(h#D0cKY?@tqD+(p=YPQ-SzW#coO~ljpXR|p1EB1G$S3j8k4}0$!*5tZv z4J(SGNKq67q>G412N95>NRy^0H58HFr9)^U3L;4FRcWDj2oM5Fuc5a<0Fe#}N{bN6 zd$aaFd#|(B_I&GnKi(hjwf-p@o+o#mbIdWun9u8JB67lt4KUMM&CMU#S+x(W?rF~ z3disf?qAX^R!iEfKRQ(mT76hHSgUjDjfh}ltTewU{_c|>AGa9lGTB4x=X_|vM!O)T zQ}3aLZMSwLL^Z2#j)+}KSg`l_4v;tU8m5U+^*pfnm^y}%yhHbj?FsiD;djlZ(_Exz z8t_&_A7dFPLocV&3q$KdXc4erG5xqBcu**5`p;fjWNdnj_9lA!Qx|LltUEP0+_ywx z`t@DB|DiX1G;9R!{UK}fS_wqU>*ps=SxC!!jkV8ZQYH*uAb6I zl%vlJwgrFLQ;FFF00kXM>=h5iKx5&f{ii(0x;!`oBB>cabtJvTi>kEv%%$$8IrSR# zD&T{hWQ~Z>4r+q;Z6!~P0`u*5tmI+y&4jzlI&LBeXS?+?7Hi(BW_N)a%+W4xpZpf-=L$a#q;RDPBYi1sUOj&y1b@2E@LB z;~3%bX_(zmGI>XJI!Ht4+2bFx`E@!w=A=eFkczk~Tv|1ROY5>}s0xowygC9$hTH$g zQZ&>Rl0SdBeH}%=%!Ig--x1Z?9t0Yo^kf&BT8w_mb|FdmIFYYAe@k?|Zo^J1NcR1; z)tXAv2kiQ5gMq2xxIp%TraUk#dd~Vdxts^GJ+Py8S@Omp3x+iz}mbSk% z9f?2-`9{10I%{!0PM_60%_-X=WdW5kp^P>M#+N?=CnT1vU{}P+7;MJNR7MK)ISj?Q zA0!y}rv%ZEC@n(m1}Z*`md!Mu%g_tfKGCb3dLv8}V_>DYWx!N>Q#Q-8_LPEJn4mD znmLcBZZrOP3MmpSRI!W;%SgRO?NI+v-ip)~T4eFk1JK!V5{7W2TEjc5fUd88v_ELbfdr`En%tapW7FtyPqzS5C{qjCT zh;BvYb0n^!!hZA-x@w$Co4j+<=59Y|*Mm`GJ(v7s*{P*} zu}rGN>zn{()r*$Vfx3AW+`@Ji%+)XEk%#Mw)5cOC$D}O1Z~>Qur*kkO5wyPO05Go8 zqV-5YsWZ0DT*~=j9ZmF3r!jHk+V*5$wcjLE$=tL5U6aeI$PZnf2}3_VUp^Jc9j<3L zcSyys>e$Gx9oNFWl`bb413hrcV!x|gf1?egT84;ho7IY1lR(Y2?WLMb{bJ$z`uhrQ zQ9Jj31Ym1}X`a0oDRx~S-d>oRB2@-Yhn5fWQKGQ@ z`m}T7^m=VM0O}1|k_?MsEEHr>3FWEy*4?ueE?@Rk6%=GX=;%ux%_=1bn)_%g@S|P7 z5jUzMQxEX8;_n!24c8X;>o2^qJYiL3e8+9-b@+@{#X$%S$tExCe)Sg|MFFy`crKZLax4Tc1%X@Lz@n?(jjKmX_?9)f;=e@1 z4$t%fea#K}J9b+{(mFuIAFIMsF9dxjG;nCy#dlmeP(^EjHg&YlFH=AD6XnzjqKULp z?*)4Jb(#RHmC#Pro8Rj_anaXASp&V+7OPQd%0JFxsB~Q0LesJ)dXROotZdvF_z|bB zH10w3HS7rR#yDO`6XdLv)LWa}QwQT~2p`~cF}Pb{t!V4=xXGyhed-aNWKY^rjz#%H zhO}-$wg`_w55_i6pBrV~P9MRL6ZUqghiaI%)+avR8@>m`mPb$_mxLwr;k+2rzRy6b z;x8aFp?V#_xV}eBGVw0sX{A2EXBb5r1+ur6 zxf=PklMWDGdwNppy6bKj?*@WC!g8e_87#sb;Rnb~ft9o2zunAJ0P=&GM!m)1tiCIr zdYl`}e9DMk#E#nbVplZ9lrQ&6*GoaQ%wA&B6fwGLSZGC73IWGJX;~HSfKHFEs{1Gl zxjY!#q8x;OJ;Kj^=0FZ*zt~)_k_B{=+885Zl-2^9Tgu@_i zvZC2qMR~D*B%mTDyH^sjP&H}p=9@dXeXoDBCEA^r#2@;F@OefaX}5M<^0#oyEq$N3 zitLU)Rrt2VefttNd2+`2oObcyhlLW8y&)L#>%%keFLSa6GNp2?6zt9JkQN^G#&w)! z^14i2ZTYyNY6}+wW#&c)#Cg}~>B5&U3+$FD@y3EqZwjb;n?jr`Yxs-ub55`czqtay z>j^lf!?_%ZftLx_wLUf0s5jOWTf9>Z zX1K0=ozk`UWRKCFP99bj4m*15+wt-~4cAQ$3YuV6RdJA|UNpfio#0_eh3EyF_RRGO}b zO*{*3c?m^@MyU8Z{L9lSu9#M9mTq$OmL!oFP`)*{6(G=YQC1bF{qBzaV$u89$p@X* zuM3O`*H)kO@*_vD>uxOeu4DZFxnQ$5(S$VI`n(B_!%Pa>`wXPy9s$2(9uCj|u3f!R zvv4LI31KNiEz@a~aZ2ofduxo>W6-jtbK;fYKUOB1`@&GM7G!TrRl!uS z0{cH;lbFH15onl31!?oyvlQEAb&d6gY4y4=9aJ}?^?7~c3Q;1rYZi|8fcOj(H2 zfUVWojrR1ys1H{~TlKT1!sJ4)-g=wiOf0@11Tz`P%*EB|(2|s|HyxVWXBzu1eQm27 z$=Q)*R+jO^CgUtsJgnyUI1LYPZwk+CV%%tg4T5!Ni5vHJmVq+ZfCGA9&5Wt&hIL6w z?z7YY?;U5p%rO?^LrC%ko%@#zg-G0%=QWV*cDRuzb~Xd;1Um|gxayeV-1!e>r;HA* zIfu>bbUgln1pUDEnoVyDj!VZQ3&^eGe=eVUI%%Gq5UUVA&}}V=#mS$Ya!HzKDN9&5 zI>udiDQS5r7Yy%2fQ2>ao#Q06G!4J+VWltc8L-!T>VOgWuN#Ji-E&T(R}ZZS zb^rDn9EbqNG+XzrZHSAiNsB)S*-mH|zH04}{mVu!*N+ST9_4Rh71$}W^`$Fa40e0~ z)dzQ{Vgd$}BPX-8HJYem%4~JxSrJW-Rl zv6$>_Hdc4v4qrJs9a(>{QA5!~ChX0`;Z?5MCjkGf)1;SR0}T}&j9{i%FaQVY?h z9jX3{t~5cm6GCx2SY26t=go!n6zZ5MH`uD=a=qp5m*v``+F=*{g18;A2G_Qs90w%^ z=dOZ;s4SD9*@gUO&-=?gOBb5Yzu8^ooAMd=Bg4pnD#Q6Jon7zRm`dG5Ozw^ZgO+2A zxYN95HJkG1Mcx#&@4g63w;IX0;kL6*C7|?}m)s=_9hsO)TB=)Dvslj};D#e|V^TjC zI);x@I4R=v5{eux^7N-!cu!3-=(N?&78c$_F5Z2cZo?9O14gy>ShkMlP3a212^%U- zn>*)cQ)#JgZ68fBx;70{J6;bcVmiE~C`E}owzdz0tLC1~C+1InwK%dM^~lc3QP^A( z{0b;_kwnBB@`-D9U3T0KbyPX`Fy}PS=&Z%^*{_bufC4+M%eL!dE})>NyE*jyQ>;6l z*4sd@H|KtU!>2I^XUudg?uL$94*Zh!vvzxi(h{ zOdg(if=r4)x0JNAHpnmI-*B*lAVB00Wy)1!g-6SWb%!ha;nT7^dux0pq40J6J&AYb zbw%<@>D$?xt+mGxPYXh7wLU&l0XXaxPQdNsjKjEXs7ZGxk)H6OyumSX!xwM2A5d4+ zk9`3R*4c=95{hWjfbHVYr#T_#b&12hw_S+UwW!D&4s{X5jhG|c6L&hRWb zV0@A>3Oq|qs#z3$Tx`I-JVDSrnq`?j!Z)Nu@)}OP!#6N@_q47_Yqycx0^a~6cVME& zmXj|=ty?nFlPq!j`!9*Alp&x(@cdfS$cu*C67zFZrn7IJ7;TQRRcvSbscMBUWHqQJ z-LniiCX(;i;7TxwuU$*GzsukQdW_{Plz6E)B)I5UbG_!T3>4_N|?N&G;(qJPkJCWfT#P!P!B5Da`ii0 zpyK=61oe^`{)=%XGLnayNqZQ{K5wHUQmfHa876P$*)CSBs_Ey?o=EDHiqzH*v=;{o z&5v^(R13H}y)bsm+`t%Mjz_(8b~l~(8C&IkhHpzT24=bRB^s2IU326Sm;^wh*3lm` zFQD%r-od_;P3(H%-^uRiF#^4%x^{OQBrFYYZX>*@n z7S0(>kA3! z7>M~KMM*s*XyC@&4;}O{2E{hX=~+ygi=lE5h`+WFVI1ok zYud5DMz-JMa@hCfNd5veZGE5;cFf(JNpW5M^v*@=b<6@?I_|JbS^zB3=Z=B(?#u)SjvLJk$O)aPdcMztfBX zY(9sW)NrHq1O4V3i|;KwAk;x`wvxVCt{EwHaX@AHI=?t687Qp!xcWiG1w z8t3Ip>%_?chj|8&(Siqny246hp#240?aiM27H1WlMH^~D2c_lVNuHkDuut%|0P+z< z7WOHh3BifQIH}C>N(gVUr?7W~#DpcLXIdai%&N=xhTdr9rpiVCgq=GFO$}n8->FVb zfQT;KVOqki(_=Blt-%k=ovzLD>p~otG5yLtb<{RgHA>#IF0_maq z_LA)+ZOOU>5;}F8jDDz;Be3E@Ce>E8_~ekB(8ov)HAeiQsP%|mG5OltV`_5_wErN~ zMHRP)70ETo@N*+i@gAXdY{Me6loQlAihsVY?-Txwc>y~LAI9=;B$|Y7ddnZ(H&~Ss zbISA%HC>zz(;h8HxKrKFPJGnaAo1i$bZ2}l<{sp;2Zc>jHG`b_gW>uyQ6S&6eC$oF--P+e|6#zeie*0@<(7U)n#9TRva^OYT_}POBykP@u&r>vk5M;;k&ib*hf0fl>$3YXZ{^9|i zoDy4`{Q6Hy^naou|1NKzZFDxXW$55>ReHYT&Jen#J}ec6X$e&wj5`%mR6RsSVl!DW zZcE&WlT=wu5I0Y}%ndCd4~TY4wxdAc>w4&6yi&(L7V>usJC{gB5T`5k$?i_By-`)} zGN~mKt_5LkGVYR6J9>K+$+OeFljdC8dk?-p4rpY#g{Q{vUnQft<}&oev7?3N{h8)S zZeT#3y@kLQg|}2_P?1?35Z3KH2w40BgA>a_wpl&X1F;OQ{V|pd`o~!q0L#Yr5AYjb z1DQOztCzMc^k21j-p}nye9^!+P%=7m7I!DAPL1rcMak#Oh3oHWQhATjWIs7*SBk;j_q-V zJQSj7&d>_GGyWh~(KQ~hO-ScO32P^CY4P;O+HM&bdUQGfbR6@?|ak+2P^mJZsuzLAmV(M^Krr(fX|9B%YG*agjI-rinP&v z+hQamZAbVLCuR2LLoqvsb>7M%geKr{FMXJz0epY$%mbw=180dJD8(Z|*|>h|{nH!; zwbrBdc6gT^kB@_uvXxxPi?2D+D{?>>Q&*A}I@-6I6=yJ4086H?8+es>ux2q*0YFd3 zD4n;2+)t&!JT+)~8cv~kl@NiC1S|L+Y*fx0Hh)usVq2CFXUI)K`f4e!OpJ1YqG$Qe zHlq{ZzSm-!c=Y!Y+>YSuVF3JarVpCrI4HDUaikj>h<#XD*)426^7eFyOKmKOAZnA3 zJk}fJwnh6}&b3%cH(&NX)XaHVblCxwKhIU~n&!0Nk-lls<=m@$jp5Saovzls3JCka z?v`Y}kp@|^Ejkk;(m>-lmFThy?B^wOKq$!-0Ug|({f&6?@4L`LnY7%@2Pwzv>eH0$ zgaQ~~p1#?HOL0R)g@%Op4m)K3zOueSFFSHHe!8pnAR`R5?v4NbjvNxoahmpVU4>TK zsqg+{XDZ_dqXBApAaj|FeH_nY8>4|xMJ3^1?#@N^K6n7HodT6%=OaRoO_SFd(;{vu z1O2R6okBuxZ}ySs$-F0Ok~elYndVg{nDA5>cgoR^hi8WE#8vvIbAITK-`U*wb%4nO zvZ=}I;rtN>Q*DsaL#Z9M%DqdLqPAHprC-&c+yxI4+d>9ugxMH<$H;o)9d=;#CL@~K zQ|{kvJuAfb(OjD{wKqr|5qkqiKo9ojt$U+uBdtT5Epjrpk5y#?bj)ymdH86$PDp6; zq4kq)<*@ApivtEXJz8496^CM_JDw?4S%tdFX%}lexjYTLBZhK~AwbsymjH}VfH)a2 zgHnCLUe#z8_Ykt-rwng&gcWo5^sR*PfQJ0OhXxn+AkqS>8@^WU^L~W0owTa&*woK$ zv>0VdKhCsWsRsFA)W&=h0vd9sLBarXauo!m3h`~JMNM=!hSPbiD}RECRy7>M;Plpa zf>jxqaxEwoPThA6qPeS($=I{E7T0-L5|kh>wN3mffGQnZUX_!h&xYnz!fPB~REl%S zUYHtr{IVxZh05hFTEZ!c>MCxnqHd|dP);}E%*nG-?rz#+8i=TTef$~w2w76I4*s@l zSZ_6F<_JQ8WCM?juC~|m{OIJcnG3VB$R%v5DN|g4h8?n|eBy0#sG0~+BqbdZ=0H6* z762iEr;V`(0!?HqqprpD2dQ8ra4Y=?vkgU|y#RZkbCp_5SX^B}$S>mw16%;R+Ow zTX&(ij1At(?VUXLE$zbUfXF~yL~Eh$b|Fy6L54i*crpsZJ}^ji5L5sOdHLS5WZ1WZ zDY5u8`;`p#cTQ3{An%9XaQuM1NC|3I07NBTOiHF^>tT|OH)Zp-QtCO_bEfdrO!V;Q z8+8GFp)~@eenjgO;%ReH-)U}%~)uw36LX7%G0=Kdd z441D8?l~MQ`I@N7tPp7K2s@{hip2-Iq5||oG$+w52A~^BPtLnEAP&TkwIhElh_mos z&OlxWt-ds^VEbjEMyEH>*~QfC$pk6|t(bxqZmLxm++2dy3lS_jIMVe^ctB-EqJv?i zPZx2Adyj?B^-Gq3Wf%a8JsxA>axbC!lP~4B@;Rsox3A0t_q~Cifw3m-qI055?TV)k>@W zAu<*8q7tHtJ4=6p+Q@qFWs8&GV*6?HG`6<|Tea5fw>?*KIRd#68><=v1sDxJ6R{{0cV$@$Zoj86n2qgtwgL(sDVO*KZ#2wMj+-ZRgV4(xL3+JnX=k3LrtBqq7B0IXhH{>0h`HHtNF8oPxp6PmTa9WiM?b_x z)gG=lOy8oo_fw($V8})bbct7`89=`<6^G^Pa--j8ojJ1>x#W_kPajYF;kVY!YfkrILc)@8_L(3_AXwnu!pEl4xOOG9KI!eD zPZDy3%nrp`8OJbpyBwfXiVxvI-o@B~8YJQDi9_sq7a<6VbuPM3mgkBaXgaW`@^+nE zyr;!>;Fg1w_%)oIP6{mN?VKK~^dG^FYPabp<1+VHtF`-C)th$>aWWJtdv~{pzxRs! zxI(;wMpu-=Cx&@+yGd!VXNofiD_@lHUfm`hu63cG{2b-#7Z2c=%&_T(&#Ex-*kM&I zy@tcR{8gP=fV4x(-FbcYSNn}8<|+y@P9_84m?G%v@gksOzsr2sjV#_GSPCts)Bi#? z)4eqPiQz{Wf&x9;tqU7|+eGeErBPv_+gAMb6)M(G_H4=82Yd`uQ(i|vrw$K6U<)s$`z{6)=f*`3WZLmk-%G^RFk-@4hSQAvi)tq6j9~#fTY5u9 z#aoh)v1Lg|UmEO`wqf1T0e;#}tJyR;_RH*UCZacJibLg~aJr-)X%w3Y=$50>QBKt_zdle5#C)@T>K_6M-Y%=M>kzsZ%31jjFshGxe?DC0 zo+@45I`v~WdodxP3aBA{aqBWJymR}SN;~k!b>Bvwt22LLrU7e{ChYW(= z0KukU^(6q=25Qc@j03pReB3wew2NH{<1g-$8EH_saQ!$+NeBcPjY-L?Gn6BR5Z=WU zEy?WHRqk2((^f%4vIgC66XK(fT?n9-KIUay*JHuvR}nR`-)D&3K)uXEC8^p~voJEg5F8 z&@^+GHZD~f4uN{=&qzuUbl@5_TO;iQ;5pJ}=e~P&f$EyYFAFl1V@%+gJAq;ozwsg7 zaeiMk^zCq|q^rT$C(=$-*d1367G3YdzEyP9wFFB=a>#|o+Sl)*x^DpX2Y|_)Vu7>4 zbK+x_8rgEn%Lk$fpawYl zsIVFrioa4$ef$gjPrKIxH%OxQ zx=Yr>8*=?PR;Ri4i%teNvUy5;n&fB!1q<;S;QETWk?iV*v`WXHrM(TlpFdZcjQqdE z@&8@JK`H&530R^{oNc!u`t9qv`1u|kXw_@)#MJGelgeB%(D4e(@(F#DuMgg4J`8%{ zxY5C(_+dq3GPX&5`uuMXAz?1DCw8yLLhP%4DT{w4ed8Q(Udu!;X#aYu|M+Um5O92U zjSP2v`DH=nPjmM7bHEb&d))qBZ^uW?-|OJ-b?|?A9gK_S#r&5Sz~5*0-*?{MXAlT; ze~W#xD>17H#ioR!23AJQCs8MbFKpy7mxwppcjrSR&$lgx-8H0Sqwz(`wAPjO>udC9 zICe>VP`mJ=vVe{BwCnnzmgL@&u?`?Y7ij;eynV3cR@i7rDO@ieTxsQEcd%5IKI z)+uq({3T%e2di4jH=sbze3|02%d=|R(tYe{uS=Y=n~SlY^eHzN8GN6ymU9UA&bi&` z3g5M!G~HlueOD*`{dH!n`GPlF2{w>v9*1BGr;yaU{=kF8SN3fwZqeF%^dTY}2M?eSaFNBc05!}fcD z?W+g9ae9Gs-&?c90RXN4r9j^YFw50p22tT3l0k-%>)4~=xWdVyl-jh}UhA@I+8Q!m z_F_ISd>8aLxt{eX<^pY7+zgP)5Sw=-{!(83yMJiE{@aLDVWqVLJK3|EgtHn$o#BI7 zbJ)T9h_`76Gb;3B-`#U<-?jnyDLEyH(nUKa?h*L(2@mWxysl7xIw#c`i@xv31kDjq zJ$rb7H#|1{pxqf$u=jd!(URc##8;t7s?u3;3bzi%hwdjnLk)HEPQ%&Uj`5`}d zv3SHJb??Ys?leH5aT8@#;}QKaR_C~!OFWl1Nct#rhE5{5`oP~|4$$thp2p#(ibSlm zwSeq-!8=UodtXynhcm2_@96?6;f8E5&ej_has(`_9*#Bups&oUWwgwgK+dUK@b1_{ z&e68g`Z}-!G#d%;)iU&!IZleialm-6{Vk1VIH=upawti~g~fWYN*(@;N_L(-=eRph z(?z_PMpIU>D`2;OeRulEwbpQTw3xdk_)5%tmsV$6n%6C*ljPpo?n?qdEu2y|_f5?-Yp@Z0zKs=c zRFg6?s_Gc_ZJ!iHN;(bnyiZ(l`?A2XiebFkp&U^J*h06pjz(q80TDXO$X|?QqK`K> zvEk=HKCX0GjHmwR15r?B+$b$?Sg#}0Lulqhx#&mqvls^(WsBPZMcou|4r+6N>(nDv zhfpO*Tom>w+Z|L!6X>aJe|$0$PXXh+20C87}BJ~8_HFQ z_AngUb@Allm&|b`hk^}S9-KOSVR+1id-WD6As)tRH#2jnW_5?`?H_YQNRI_D`NpBX z#dhf~3aWR+2nJ5N9h_62UwdE_b>#Tk1bL${S|piIr$T)rzG-fm++5*P21)Li+<_g_ zO}ftlrv0yV9J~4DlZr1&jf#Iw13EP5AP@u}Q@xhGNBw|#MV$EANv<^S`r9UTyXi(H zfJmi~5wP8TbKPm?l_xOH3v9FbQyjp9W<+cuKBGLc^Vy=ml z6N$0M1sk9thyzAoJqAPcxIqGSu3>Q%9>e(a053I3-S50>kp(Pf2C`iQIbL2Bb@&PF zv6Pr#SMHQc9d0MPqB%Ea4o+n$L?A!a)@~=c-{(8p-#b_rTB?%+9z{bUNA@?4jt%+$Is_L`YQ zz4`S!^@pd4@8@mpv5rYUdC$tKaryLp%FFjDBY2pRcyjsT_3l>8MAv-iT;wGCq`l;$ zs%{+0Cr%$1$sySE;d|Pq*uq~*+&>=yxMtt}8khX{>y(=2KhKDI@5c6{bY%xR;x*rf zEXR|)Imm#X^4#9UYEqa!F(R`_G)(2Dp-%31LFDs|m0=e%(B0Z`+$t}0`ZA*5@P5O6o` z!5a^6Rl)j{+d3NSHoqkE90M!=+8gh5z*)|}%3v(-1WZw`?O7QL209l+4O+l9#SHP3 zu7)4fch}A)&CMi~q<#_0Hy-7Qow~Z=aB!$3@RM-hxV@mCAyEq}i8U2w_>gv?EfrNC(F2Nx$(>?hJPy)H-1|&vUxn7}`$W68Esu6h))viNzy=Ei4csT< zMZ(!b zu75QZ$sp1RNx<5P=DyZ2OPDOmiS9`2jNf)+wmA$K6nKu3-0a(^q#G%Hm>NA&mwGoJ z%HwG@CzpS=RK#jAdenN6Z{_y5qb3wJW;|>tm{WIMX$^F)C7q71kl9J8=XqOpBg(wX zJT_5wFd=_-YxR?ie5OaRX6%<}-nL-I`o5&uNzZ2ucqs7s+it{eG^GTCoQ97Pnak(TKsZ4OytmYerPb-%}M+df(PZ>Fxsl-gyFtErwNdcdLNMYYY zQ&eYdWrNNjhgxc^IUT%YVH$f_C1gJ`mN-g<$6=yW?Oosxj8Kc8{O!vZ_n+Hl1nLb* z*p3I2SE^Z@ilaBBSo-1EqfS_+-fJe)m=wTz*HlXBgEDXe>? z1`4ga(Sk2s!L~Jh6r-?Vu4*NW+t?O&T-#Tvaqr<@ORGuuIvf)Cq<_&#bDx34TFN1? zV8X5AuED8cr_sm5{&D#m9kWdv^XqLP8e#LwEQ3?G&6JBV-cQ85c8e=Jq=hq0jA1P7 zg)z!+sD3(O#XJy7+Mg}@8`rPP1Z;g>aTgnetuyOXdA%)7z)4swY!zQOsCy^lteVCz zFS++q&>A1?o5;01DE=A>4k^a^?|{nrp(Kn;tLRY=nydgjeKbv(K_lSgCqX$<{p8g0 zAR?)z6FO|f$8cV-D>BVDyuxzeYPnQ51Nk$T<#hY)-Z^WtT(!?s~Qt1pI zFp%U6J0p`3juUqcG6ra~7L1EL*L9Jr>D`B`(NB0)%1)Q1`-&Rxp)dsX(rH7@bt9;e zUw22QM@zRlkD*mZ5@+uD{Hq$fO^TmIyEB_0pmyVsH$QF@OrzW7cPcRC1M8~kqvCr` zJIVJj9}_A!6{*@3f4q?W;Ni(!VD=$Tz_FP*Kh?XDac@Ave3`ML`-S0Ro*S=zZ%trD z&9q8XvS=ASqR&W}L3c3g!EV{cMprNWAj)^?)iXHGyRgM9N0nkXXX1J6(oX$?Ds%Vf zg3IbiB#Zx)U=rikO8Mf$vKKA4tUZeKL^Ca;N8*pq zk&MvsQy!N{thEFY&RzmqZ@x!M0lG)GM%6mn;*-ragZ`B-78}L#rfUOiJou=)_q}q} zZ+@uTdZ@`6Gs=GCjeg_Np1^W!7bf9O0q z_!L-VGj#X%k}1U-uGr@Cn;<=Ih8c`r;Nk7K^mhAUh0(O> z@~^{Wia~hQ@BpceXS6?sKj? zuY=Qjo6sA1*Wrcr6ej2_apMd0qzxSR)po1|FH3=-)#?9Kk}6TFP-fD>n&0U7`!M0( z0mC2u^gZf?AD`5h)*U-A1mu;D3V|F>x+>~A%(+sI{>gRDUAk2{@|GR3JYE(pqLfuL z81+a&?@QM7PA`VS&YJ8cMzz{d3(w8=A=O(07+Xni&poihn61@vs|cAkEMXW!bF zb0DdJW+B%mE8F5bQYG?%K2GD(bp=ZHFdS{3#_H&N>xZOBJ|iNJ-K@nd;8jIg;MGDm zQssYUhadXBjC$;b9j2CD?OCBUH^2Wg=zZqXjcZM;BOYbhLiUVz_A`bpXsC8K7Ggm< z(wQnjn9GWr&=p=l?aDbWbQ|>`#D9DQ?IG2a6UE^Wd|8#{Aa#FJSP{X~|C*$iklvL8 ze7aUx(brgh|NoTv|LJFkpq9Ge`%PQ)0a)>vkJE0+ac~%}46RxCQg^r7#mL$~?V6;M zOe$@8hwhzEwio-2ZZ8f%_wV#0Lzib2nF~wg{Lk-3-pKPs0r?CBON$pCzb@tuG_AC7o30Hbk z)OGKrwoEodvHQafWmcoI#s*Br!=FnSI}08;I6uG-4VOG2t$F$7YepcRUc$;EQ$_-Z zM#>wk_sKf*8r9BeNWADtU+pMh>iO)7g-KnCo8*?4DJxNr+IX)aPd+7YCy>>Sq^|QWZzuwFr zZ3!+X$d{W~p>Gq#1x`Zn>AhZ8%ZZ{WQ7$UUBW=*t&#q_Ul##IYvzQ3BGdio?LGeOT z&P(t1%WV<;RN8g!toF_%tHeDvz3H+_rZY1a7Yg<(o`;%r$A3vQ|E+5KujJwnXT`t% zx({?`-AApZj;FyLpF|PxXtfuvgY|qyTxjM@PMW!NMdM6SR3CjPW5||QSb)cz$CbMG zOj4-@AjpCCOtR4wWBT($z|yuk%3U3pYwB6ZR^N>f5}1AaI`Wn!k@a|hcU)FaGj_x) zp)S{CV^#LUE^c})(sZI1vRc2ctG;1MzG}1_X{8lKai^<>3;scDemFdu3f(UgPn>;Y z!AHvQP-KOTz-l>>9W#`tqyP4!Bkf5T=(ErjMoQf=`qW(8h!6QCC&E7qx_xV;sx2%n zyj8`ZVj(!^)leW)2no&kBq5JHWe{y@z&zoo4ZY19BVJ&RaCwY~L(L>wEX7=4UJ6Sq;zD}XvjtsZ z7Z_Q^PPn;uo&T`>e6lQSvx#h6)M4NJ>$$^ecFdD6D# zHwj4C)Rk$~G9y@oi7@V)=G#o&2<#O^XpL_++fKM$MJgrG0}*x8k|>u^W66oq`;&^hF5 zf!2JHZndtFix8p8SWLaQJa7QLDei_Xja}x8tEzVY8o6I-hpg07tD8F1u-w$mf7x}% z4$aCiwRFiY0cTra71Oz4*5*305ic-$Ulg-B{O$%kl4yyp(Jq5)pllC=CbALmdh zf_g}4Fr{+b+SS?t7Z|P-c^8=_-m$#C?&_NkdvDQ&eSUpv=yUDveP#Nd4)_&vBWnX1U29eg4i`-?< z+rQ?B{Qi77M)o$5meOmbjh?<8J>vL~D(9jkCG-#pJq}glhs<7&O@jw)4+zihzE&?b zIg^jp5H^|nyuQ<6FOGsBUoXcDlfq&Sdp5e%E3pb!x(C?vq_sP*vy@pq_sB>+puqHx z?Qg^8lQh~)kCs<=G+HYdZ*25%I6YeS%4IDWB&}kqVU=0L#`%Wne<+cZuWcU4GO)|t z)`nhr{=OuU_>SKothYUReS!8!9DhFB6P^cTXZ^t%trwPUA+pIsC$p|J9I-rYNVv$- z4T1@|V*r8wc9y7Dx;Mp+w03#+hs-v_SI>5V)fEI?&~&GDrumkxBvL^Yy5kJ3ScI)LooGV`w~Gvr zH#U9u6|;Ne+0df~6>o9N`F1I2%@c$525P|S=Bb?T_n_O5Dpox;Ti*g*D>Aj*RS#oH z=dqhypJ9RPLrv~-8mP1eW%5$p^yTsY? zy5=~NTWiBO)%qUQUz74tCvLz!Y7L=f2bfZX9o^FfF`=BJ&}0cu10cJ)0mzNpOUbMf zCpG_4PWnmRi?5ctQ5%>44WLW8RO$o|K{{2w0q_k(_8k^Mb%$LZ|fi{bCZ z@Gn^7@5S){@M75gsT;kv4GY7!;~3@qdf1qJuc`$*J@|dx^grA@?@U3^DOrEwbfedW zy>MKp^q36PNR<13_{8sW2IkmffcqyAh|2hvWNH!i6LUTay&;2t@!_u_A1%Jv-1i3e zqnFn%_)S;0STGEJC4pOwj=F#8;-lOGpS>2xH}xS2z8;83#IJWrY#(`VHz0vg#b`Xz zZ9TOVmzp>#jGIDc95!(1l$(@knbZBH9O$1Z(Px#DP!cDq_f~)Dh5o^Ek^Tq*Rnjf4 zhlD@mRsZ8(MZjRzoRby)Yh(Z0{4)1}&k*ljN%#I=yxprLFldu+(&_$nKKy@UER;*& zGeigH`z8MuZwH2s$jI)&lfOdh_^&A;aR#69^LcB*^uKt!nQVYdihRoXH3j_7-|%!T zh)Sg!9#l~L*Q0;8uz$6AUOs iR00*{`~O{ipdQ(g(TpM?)6$uX+A|e!FT;0Gg=W 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

lwkj|aOKBuMoe<<0=$TnPh7aaQF^A1d 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

$V4mX45F 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 literal 0 HcmV?d00001 diff --git a/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc new file mode 100644 index 0000000000000..c57e9876a4118 --- /dev/null +++ b/docs/user/alerting/troubleshooting/alerting-common-issues.asciidoc @@ -0,0 +1,253 @@ +[role="xpack"] +[[alerting-common-issues]] +=== Common Issues + +This page describes how to resolve common problems you might encounter with Alerting. + +[float] +[[rules-small-check-interval-run-late]] +==== Rules with small check intervals run late + +*Problem* + +Rules with a small check interval, such as every two seconds, run later than scheduled. + +*Solution* + +Rules run as background tasks at a cadence defined by their *check interval*. +When a Rule *check interval* is smaller than the Task Manager <>, the rule will run late. + +Either tweak the <> or increase the *check interval* of the rules in question. + +For more details, see <>. + + +[float] +[[scheduled-rules-run-late]] +==== Rules with the inconsistent cadence + +*Problem* + +Scheduled rules run at an inconsistent cadence, often running late. + +Actions run long after the status of a rule changes, sending a notification of the change too late. + +*Solution* + +Rules and actions run as background tasks by each {kib} instance at a default rate of ten tasks every three seconds. +When diagnosing issues related to Alerting, focus on the tasks that begin with `alerting:` and `actions:`. + +Alerting tasks always begin with `alerting:`. For example, the `alerting:.index-threshold` tasks back the <>. +Action tasks always begin with `actions:`. For example, the `actions:.index` tasks back the <>. + +For more details on monitoring and diagnosing task execution in Task Manager, see <>. + +[float] +[[connector-tls-settings]] +==== Connectors have TLS errors when executing actions + +*Problem* + +When executing actions, a connector gets a TLS socket error when connecting to +the server. + +*Solution* + +Configuration options are available to specialize connections to TLS servers, +including ignoring server certificate validation, and providing certificate +authority data to verify servers using custom certificates. For more details, +see <>. + +[float] +[[rules-long-execution-time]] +==== Rules take a long time to run + +*Problem* + +Rules are taking a long time to execute and are impacting the overall health of your deployment. + +[IMPORTANT] +============================================== +By default, only users with a `superuser` role can query the {kib} event log because it is a system index. To enable additional users to execute this query, assign `read` privileges to the `.kibana-event-log*` index. +============================================== + +*Solution* + +Query for a list of rule ids, bucketed by their execution times: + +[source,console] +-------------------------------------------------- +GET /.kibana-event-log*/_search +{ + "size": 0, + "query": { + "bool": { + "filter": [ + { + "range": { + "@timestamp": { + "gte": "now-1d", <1> + "lte": "now" + } + } + }, + { + "term": { + "event.action": { + "value": "execute" + } + } + }, + { + "term": { + "event.provider": { + "value": "alerting" <2> + } + } + } + ] + } + }, + "runtime_mappings": { <3> + "event.duration_in_seconds": { + "type": "double", + "script": { + "source": "emit(doc['event.duration'].value / 1E9)" + } + } + }, + "aggs": { + "ruleIdsByExecutionDuration": { + "histogram": { + "field": "event.duration_in_seconds", + "min_doc_count": 1, + "interval": 1 <4> + }, + "aggs": { + "ruleId": { + "nested": { + "path": "kibana.saved_objects" + }, + "aggs": { + "ruleId": { + "terms": { + "field": "kibana.saved_objects.id", + "size": 10 <5> + } + } + } + } + } + } + } +} +-------------------------------------------------- +// TEST + +<1> This queries for rules executed in the last day. Update the values of `lte` and `gte` to query over a different time range. +<2> Use `event.provider: actions` to query for long-running action executions. +<3> Execution durations are stored as nanoseconds. This adds a runtime field to convert that duration into seconds. +<4> This interval buckets the `event.duration_in_seconds` runtime field into 1 second intervals. Update this value to change the granularity of the buckets. If you are unable to use runtime fields, make sure this aggregation targets `event.duration` and use nanoseconds for the interval. +<5> This retrieves the top 10 rule ids for this duration interval. Update this value to retrieve more rule ids. + +This query returns the following: + +[source,json] +-------------------------------------------------- +{ + "took" : 322, + "timed_out" : false, + "_shards" : { + "total" : 1, + "successful" : 1, + "skipped" : 0, + "failed" : 0 + }, + "hits" : { + "total" : { + "value" : 326, + "relation" : "eq" + }, + "max_score" : null, + "hits" : [ ] + }, + "aggregations" : { + "ruleIdsByExecutionDuration" : { + "buckets" : [ + { + "key" : 0.0, <1> + "doc_count" : 320, + "ruleId" : { + "doc_count" : 320, + "ruleId" : { + "doc_count_error_upper_bound" : 0, + "sum_other_doc_count" : 0, + "buckets" : [ + { + "key" : "1923ada0-a8f3-11eb-a04b-13d723cdfdc5", + "doc_count" : 140 + }, + { + "key" : "15415ecf-cdb0-4fef-950a-f824bd277fe4", + "doc_count" : 130 + }, + { + "key" : "dceeb5d0-6b41-11eb-802b-85b0c1bc8ba2", + "doc_count" : 50 + } + ] + } + } + }, + { + "key" : 30.0, <2> + "doc_count" : 6, + "ruleId" : { + "doc_count" : 6, + "ruleId" : { + "doc_count_error_upper_bound" : 0, + "sum_other_doc_count" : 0, + "buckets" : [ + { + "key" : "41893910-6bca-11eb-9e0d-85d233e3ee35", + "doc_count" : 6 + } + ] + } + } + } + ] + } + } +} +-------------------------------------------------- +<1> Most rule execution durations fall within the first bucket (0 - 1 seconds). +<2> A single rule with id `41893910-6bca-11eb-9e0d-85d233e3ee35` took between 30 and 31 seconds to execute. + +Use the <> to retrieve additional information about rules that take a long time to execute. + +[float] +[[rule-cannot-decrypt-api-key]] +=== Rule cannot decrypt apiKey + +*Problem*: + +The rule fails to execute and has an `Unable to decrypt attribute "apiKey"` error. + +*Solution*: + +This error happens when the `xpack.encryptedSavedObjects.encryptionKey` value used to create the rule does not match the value used during rule execution. Depending on the scenario, there are different ways to solve this problem: + +[cols="2*<"] +|=== + +| If the value in `xpack.encryptedSavedObjects.encryptionKey` was manually changed, and the previous encryption key is still known. +| Ensure any previous encryption key is included in the keys used for <>. + +| If another {kib} instance with a different encryption key connects to the cluster. +| The other {kib} instance might be trying to run the rule using a different encryption key than what the rule was created with. Ensure the encryption keys among all the {kib} instances are the same, and setting <> for previously used encryption keys. + +| If other scenarios don't apply. +| Generate a new API key for the rule by disabling then enabling the rule. + +|=== diff --git a/docs/user/alerting/troubleshooting/event-log-index.asciidoc b/docs/user/alerting/troubleshooting/event-log-index.asciidoc new file mode 100644 index 0000000000000..fa5b5831c04ee --- /dev/null +++ b/docs/user/alerting/troubleshooting/event-log-index.asciidoc @@ -0,0 +1,201 @@ +[role="xpack"] +[[event-log-index]] +=== Event log index + +Use the event log index to determine: + +* Whether a rule successfully ran but its associated actions did not +* Whether a rule was ever activated +* Additional information about rule execution errors +* Duration times for rule and action executions + +[float] +==== Example Event Log Queries + +Event log query to look at all event related to a specific rule id: +[source, txt] +-------------------------------------------------- +GET /.kibana-event-log*/_search +{ + "sort": [ + { + "@timestamp": { + "order": "desc" + } + } + ], + "query": { + "bool": { + "filter": [ + { + "term": { + "event.provider": { + "value": "alerting" + } + } + }, + // optionally filter by specific action event + { + "term": { + "event.action": "active-instance" + | "execute-action" + | "new-instance" + | "recovered-instance" + | "execute" + } + }, + // filter by specific rule id + { + "nested": { + "path": "kibana.saved_objects", + "query": { + "bool": { + "filter": [ + { + "term": { + "kibana.saved_objects.id": { + "value": "b541b690-bfc4-11eb-bf08-05a30cefd1fc" + } + } + }, + { + "term": { + "kibana.saved_objects.type": "alert" + } + } + + ] + } + } + } + } + ] + } + } +} +-------------------------------------------------- + +Event log query to look at all events related to executing a rule or action. These events include duration. +[source, txt] +-------------------------------------------------- +GET /.kibana-event-log*/_search +{ + "sort": [ + { + "@timestamp": { + "order": "desc" + } + } + ], + "query": { + "bool": { + "filter": [ + { + "term": { + "event.action": { + "value": "execute" + } + } + }, + // optionally filter by specific rule or action id + { + "nested": { + "path": "kibana.saved_objects", + "query": { + "bool": { + "filter": [ + { + "term": { + "kibana.saved_objects.id": { + "value": "b541b690-bfc4-11eb-bf08-05a30cefd1fc" + } + } + } + ] + } + } + } + } + ] + } + } +} +-------------------------------------------------- + +Event log query to look at the errors. +You should see an `error.message` property in that event, with a message from the action executor that might provide more detail on why the action encountered an error: +[source, txt] +-------------------------------------------------- +{ + "event": { + "provider": "actions", + "action": "execute", + "start": "2020-03-31T04:27:30.392Z", + "end": "2020-03-31T04:27:30.393Z", + "duration": 1000000 + }, + "kibana": { + "namespace": "default", + "saved_objects": [ + { + "type": "action", + "id": "7a6fd3c6-72b9-44a0-8767-0432b3c70910" + } + ], + }, + "message": "action executed: .server-log:7a6fd3c6-72b9-44a0-8767-0432b3c70910: server-log", + "@timestamp": "2020-03-31T04:27:30.393Z", +} +-------------------------------------------------- + +And see the errors for the rules you might provide the next search query: +[source, txt] +-------------------------------------------------- +{ + "event": { + "provider": "alerting", + "start": "2020-03-31T04:27:30.392Z", + "end": "2020-03-31T04:27:30.393Z", + "duration": 1000000 + }, + "kibana": { + "namespace": "default", + "saved_objects": [ + { + "rel" : "primary", + "type" : "alert", + "id" : "30d856c0-b14b-11eb-9a7c-9df284da9f99" + } + ], + }, + "message": "alert executed: .index-threshold:30d856c0-b14b-11eb-9a7c-9df284da9f99: 'test'", + "error" : { + "message" : "Saved object [action/ef0e2530-b14a-11eb-9a7c-9df284da9f99] not found" + }, +} +-------------------------------------------------- + +You can also query the event log for failures, which should return more specific details about rules which failed by targeting the event.outcome: + +[source, txt] +-------------------------------------------------- +GET .kibana-event-log-*/_search +{ + "query": { + "bool": { + "must": [ + { "match": { "event.outcome": "failure" }} + ] + } + } +} +-------------------------------------------------- + +Here’s an example of what failed credentials from Google SMTP might look like from the response: +[source, txt] +-------------------------------------------------- +"error" : { + "message" : """error sending email: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at +535 5.7.8 https://support.google.com/mail/?p=BadCredentials e207sm3359731pfh.171 - gsmtp""" +}, +-------------------------------------------------- diff --git a/docs/user/alerting/troubleshooting/testing-connectors.asciidoc b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc new file mode 100644 index 0000000000000..c99ac243f0ad3 --- /dev/null +++ b/docs/user/alerting/troubleshooting/testing-connectors.asciidoc @@ -0,0 +1,72 @@ +[role="xpack"] +[[testing-connectors]] +=== Test connectors + + +By using Kibana Management UI you can test a newly created Connector by navigating to the Test tab of Connector Edit flyout or by clicking "Save & test" button on Create flyout: +[role="screenshot"] +image::user/alerting/images/connector-save-and-test.png[Rule management page with the errors banner] + +or by directly opening the proper connector Edit flyout: +[role="screenshot"] +image::user/alerting/images/email-connector-test.png[Rule management page with the errors banner] + +[role="screenshot"] +image::user/alerting/images/teams-connector-test.png[Five clauses define the condition to detect] + +[float] +==== experimental[] Troubleshooting Connectors with `kbn-action` tool + +Executing an Email action via https://github.com/pmuellr/kbn-action[kbn-action]. In this example, is using a cloud deployment of the stack: + +[source] +-------------------------------------------------- +$ npm -g install pmuellr/kbn-action + +$ export KBN_URLBASE=https://elastic:@.us-east-1.aws.found.io:9243 + +$ kbn-action ls +[ + { + "id": "a692dc89-15b9-4a3c-9e47-9fb6872e49ce", + "actionTypeId": ".email", + "name": "gmail", + "config": { + "from": "test@gmail.com", + "host": "smtp.gmail.com", + "port": 465, + "secure": true, + "service": null + }, + "isPreconfigured": false, + "referencedByCount": 0 + } +] +-------------------------------------------------- +and then execute this: + +[source] +-------------------------------------------------- +$ kbn-action execute a692dc89-15b9-4a3c-9e47-9fb6872e49ce '{subject: "hallo", message: "hallo!", to:["test@yahoo.com"]}' +{ + "status": "ok", + "data": { + "accepted": [ + "test@yahoo.com" + ], + "rejected": [], + "envelopeTime": 100, + "messageTime": 955, + "messageSize": 521, + "response": "250 2.0.0 OK 1593144408 r5sm8625873qtc.20 - gsmtp", + "envelope": { + "from": "test@gmail.com", + "to": [ + "test@yahoo.com" + ] + }, + "messageId": "" + }, + "actionId": "a692dc89-15b9-4a3c-9e47-9fb6872e49ce" +} +-------------------------------------------------- diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index e4faa81c174e9..82f3355759b67 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -105,4 +105,5 @@ include::{kib-repo-dir}/api/actions-and-connectors.asciidoc[] include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] include::{kib-repo-dir}/api/url-shortening.asciidoc[] +include::{kib-repo-dir}/api/task-manager/health.asciidoc[] include::{kib-repo-dir}/api/upgrade-assistant.asciidoc[] diff --git a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc index 4b63313b2b96e..363562d4cd193 100644 --- a/docs/user/production-considerations/task-manager-troubleshooting.asciidoc +++ b/docs/user/production-considerations/task-manager-troubleshooting.asciidoc @@ -955,3 +955,73 @@ Tasks are not running, and the server logs contain the following error message: Inline scripts are a hard requirement for Task Manager to function. To enable inline scripting, see the Elasticsearch documentation for {ref}/modules-scripting-security.html#allowed-script-types-setting[configuring allowed script types setting]. + +[float] +[[task-runat-is-in-the-past]] +==== What do I do if the Task’s `runAt` is in the past? + +*Problem*: + +Tasks' property `runAt` is in the past. + +*Solution*: + +Wait a bit before declaring it as a lost cause, as Task Manager might just be falling behind on its work. +You should take a look at the Kibana log and see what you can find that relates to Task Manager. +In a healthy environment you should see a log line that indicates that Task Manager was successfully started when Kibana was: +[source, txt] +-------------------------------------------------- +server log [12:41:33.672] [info][plugins][taskManager][taskManager] TaskManager is identified by the Kibana UUID: 5b2de169-2785-441b-ae8c-186a1936b17d +-------------------------------------------------- + +If you see that message and no other errors that relate to Task Manager, it’s most likely that Task Manager is running fine and has simply not had the chance to pick the task up yet. +If, on the other hand, the runAt is severely overdue, then it’s worth looking for other Task Manager or Alerting related errors, as something else may have gone wrong. +It’s worth looking at the status field, as it might have failed, which would explain why it hasn’t been picked up or it might be running which means the task might simply be a very long running one. + +[float] +[[task-marked-failed]] +==== What do I do if the Task is marked as failed? + +*Problem*: + +Tasks marked as failed. + +*Solution*: + +Broadly speaking the Alerting framework is meant to gracefully handle the cases where a task is failing by rescheduling a fresh run in the future. If this fails to happen, then that means something has gone wrong in the underlying implementation and this isn’t expected. +Ideally you should try and find any log lines that relate to this rule and its task, and use these to help us investigate further. + +[float] +[[task-manager-kibana-log]] +==== Task Manager Kibana Log +Task manager will write log lines to the Kibana Log on certain occasions. Below are some common log lines and what they mean. + +Task Manager has run out of Available Workers: +[source, txt] +-------------------------------------------------- +server log [12:41:33.672] [info][plugins][taskManager][taskManager] [Task Ownership]: Task Manager has skipped Claiming Ownership of available tasks at it has ran out Available Workers. +-------------------------------------------------- + +This log message tells us that Task Manager is not managing to keep up with the sheer amount of work it has been tasked with completing. This might mean that Rules are not running at the frequency that was expected (instead of running every 5 minutes, it runs every 7-8 minutes, just as an example). + +By default Task Manager is limited to 10 tasks and this can be bumped up by setting a higher number in the kibana.yml file using the `xpack.task_manager.max_workers` configuration. It is important to keep in mind that a higher number of tasks running at any given time means more load on both Kibana and Elasticsearch, so only change this setting if increasing load in your environment makes sense. + +Another approach to addressing this might be to tell workers to run at a higher rate, rather than adding more of them, which would be configured using xpack.task_manager.poll_interval. This value dictates how often Task Manager checks to see if there’s more work to be done and uses milliseconds (by default it is 3000, which means an interval of 3 seconds). + +Before changing either of these numbers it’s highly recommended to investigate what Task Manager can’t keep up - Are there an unusually high number of rules in the system? Are rules failing often, forcing Task Manager to re-run them constantly? Is Kibana under heavy load? There could be a variety of issues, none of which should be solved by simply changing these configurations. + +Task TaskType failed in attempt to run: +[source, txt] +-------------------------------------------------- +server log [12:41:33.672] [info][plugins][taskManager][taskManager] Task TaskType "alerting:example.always-firing" failed in attempt to run: Unable to load resource ‘/api/something’ +-------------------------------------------------- + +This log message tells us that when Task Manager was running one of our rules, it’s task errored and, as a result, failed. In this case we can tell that the rule that failed was of type alerting:example.always-firing and that the reason it failed was Unable to load resource ‘/api/something’ . This is a contrived example, but broadly, if you see a message with this kind of format, then this tells you a lot about where the problem might be. + +For example, in this case, we’d expect to see a corresponding log line from the Alerting framework itself, saying that the rule failed. You should look in the Kibana log for a line similar to the log line below (probably shortly before the Task Manager log line): + +Executing Alert "27559295-44e4-4983-aa1b-94fe043ab4f9" has resulted in Error: Unable to load resource ‘/api/something’ + +This would confirm that the error did in fact happen in the rule itself (rather than the Task Manager) and it would help us pin-point the specific ID of the rule which failed: 27559295-44e4-4983-aa1b-94fe043ab4f9 + +We can now use the ID to find out more about that rule by using the http endpoint to find that rule’s configuration and current state to help investigate what might have caused the issue. From 928c4b8353c4d4696dc9966d7ab0491ed03d6ba9 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 28 Jun 2021 13:00:26 -0600 Subject: [PATCH 15/74] Convert image names to snake_case in RFC 0020. (#103536) --- .../cluster_mode.png} | Bin .../no_cluster_mode.png} | Bin .../perf_4_workers.png} | Bin .../perf_clustering_2_worker.png} | Bin .../perf_no_clustering.png} | Bin rfcs/text/0020_nodejs_clustering.md | 10 +++++----- 6 files changed, 5 insertions(+), 5 deletions(-) rename rfcs/images/{15_clustering/cluster-mode.png => 20_clustering/cluster_mode.png} (100%) rename rfcs/images/{15_clustering/no-cluster-mode.png => 20_clustering/no_cluster_mode.png} (100%) rename rfcs/images/{15_clustering/perf-4-workers.png => 20_clustering/perf_4_workers.png} (100%) rename rfcs/images/{15_clustering/perf-clustering-2-worker.png => 20_clustering/perf_clustering_2_worker.png} (100%) rename rfcs/images/{15_clustering/perf-no-clustering.png => 20_clustering/perf_no_clustering.png} (100%) diff --git a/rfcs/images/15_clustering/cluster-mode.png b/rfcs/images/20_clustering/cluster_mode.png similarity index 100% rename from rfcs/images/15_clustering/cluster-mode.png rename to rfcs/images/20_clustering/cluster_mode.png diff --git a/rfcs/images/15_clustering/no-cluster-mode.png b/rfcs/images/20_clustering/no_cluster_mode.png similarity index 100% rename from rfcs/images/15_clustering/no-cluster-mode.png rename to rfcs/images/20_clustering/no_cluster_mode.png diff --git a/rfcs/images/15_clustering/perf-4-workers.png b/rfcs/images/20_clustering/perf_4_workers.png similarity index 100% rename from rfcs/images/15_clustering/perf-4-workers.png rename to rfcs/images/20_clustering/perf_4_workers.png diff --git a/rfcs/images/15_clustering/perf-clustering-2-worker.png b/rfcs/images/20_clustering/perf_clustering_2_worker.png similarity index 100% rename from rfcs/images/15_clustering/perf-clustering-2-worker.png rename to rfcs/images/20_clustering/perf_clustering_2_worker.png diff --git a/rfcs/images/15_clustering/perf-no-clustering.png b/rfcs/images/20_clustering/perf_no_clustering.png similarity index 100% rename from rfcs/images/15_clustering/perf-no-clustering.png rename to rfcs/images/20_clustering/perf_no_clustering.png diff --git a/rfcs/text/0020_nodejs_clustering.md b/rfcs/text/0020_nodejs_clustering.md index 62c4de4d3cbe4..9ee5d764c8de1 100644 --- a/rfcs/text/0020_nodejs_clustering.md +++ b/rfcs/text/0020_nodejs_clustering.md @@ -59,14 +59,14 @@ container per host CPU with a single Kibana process per container). In 'classic' mode, the Kibana server is started in the main Node.js process. -![image](../images/15_clustering/no-cluster-mode.png) +![image](../images/20_clustering/no_cluster_mode.png) In clustering mode, the main Node.js process would only start the coordinator, which would then fork workers using Node's `cluster` API. Node's underlying socket implementation allows multiple processes to listen to the same ports, effectively performing http traffic balancing between the workers for us. -![image](../images/15_clustering/cluster-mode.png) +![image](../images/20_clustering/cluster_mode.png) The coordinator's primary responsibility is to orchestrate the workers. It would not be a 'super' worker handling both the job of a worker while being in charge of managing the other workers. @@ -97,15 +97,15 @@ Intel Core i9 - 32 GB 2400 MHz DDR4), using the default configuration of the `ki #### Non-clustered mode -![image](../images/15_clustering/perf-no-clustering.png) +![image](../images/20_clustering/perf_no_clustering.png) #### Clustered mode, 2 workers -![image](../images/15_clustering/perf-clustering-2-worker.png) +![image](../images/20_clustering/perf_clustering_2_worker.png) #### Clustered mode, 4 workers -![image](../images/15_clustering/perf-4-workers.png) +![image](../images/20_clustering/perf_4_workers.png) ### 4.1.2 Analysis From 8a57c9dad5e07813c24ea8130a8bb11ff44d8fe1 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 28 Jun 2021 15:13:20 -0400 Subject: [PATCH 16/74] [ML] Anomaly Detection: Visualize delayed - data Part 3 (#103150) * move content from modal to flyout with message table below chart * update file name from modal to flyout * update messages endpoint for range to use with chart range * add show in chart action for messages table * add job messages title and make flyout smaller --- .../annotations_table/annotations_table.js | 12 +- .../components/job_messages/job_messages.tsx | 55 +++- .../constants.ts | 4 +- .../datafeed_chart_flyout.tsx} | 261 ++++++++++-------- .../edit_query_delay.tsx | 14 +- .../index.ts | 2 +- .../components/job_details/job_details.js | 12 +- .../job_details/job_messages_pane.tsx | 74 ++--- .../services/ml_api_service/jobs.ts | 18 +- .../job_audit_messages.d.ts | 8 +- .../job_audit_messages/job_audit_messages.js | 13 +- .../ml/server/routes/job_audit_messages.ts | 11 +- .../schemas/job_audit_messages_schema.ts | 6 +- 13 files changed, 305 insertions(+), 185 deletions(-) rename x-pack/plugins/ml/public/application/jobs/jobs_list/components/{datafeed_modal => datafeed_chart_flyout}/constants.ts (88%) rename x-pack/plugins/ml/public/application/jobs/jobs_list/components/{datafeed_modal/datafeed_modal.tsx => datafeed_chart_flyout/datafeed_chart_flyout.tsx} (69%) rename x-pack/plugins/ml/public/application/jobs/jobs_list/components/{datafeed_modal => datafeed_chart_flyout}/edit_query_delay.tsx (90%) rename x-pack/plugins/ml/public/application/jobs/jobs_list/components/{datafeed_modal => datafeed_chart_flyout}/index.ts (80%) diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index b68e64a5d9f6a..20d3f8e03ddbc 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -51,7 +51,7 @@ import { ML_APP_URL_GENERATOR, ML_PAGES } from '../../../../../common/constants/ import { PLUGIN_ID } from '../../../../../common/constants/app'; import { timeFormatter } from '../../../../../common/util/date_utils'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; -import { DatafeedModal } from '../../../jobs/jobs_list/components/datafeed_modal'; +import { DatafeedChartFlyout } from '../../../jobs/jobs_list/components/datafeed_chart_flyout'; const CURRENT_SERIES = 'current_series'; /** @@ -79,7 +79,7 @@ class AnnotationsTableUI extends Component { this.props.jobs[0] !== undefined ? this.props.jobs[0].job_id : undefined, - datafeedModalVisible: false, + datafeedFlyoutVisible: false, datafeedEnd: null, }; this.sorting = { @@ -509,7 +509,7 @@ class AnnotationsTableUI extends Component { iconType="visAreaStacked" onClick={() => this.setState({ - datafeedModalVisible: true, + datafeedFlyoutVisible: true, datafeedEnd: annotation.end_timestamp, }) } @@ -727,11 +727,11 @@ class AnnotationsTableUI extends Component { search={search} rowProps={getRowProps} /> - {this.state.jobId && this.state.datafeedModalVisible && this.state.datafeedEnd ? ( - { this.setState({ - datafeedModalVisible: false, + datafeedFlyoutVisible: false, }); }} end={this.state.datafeedEnd} diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx index 6ff348860253e..d0666ac2c660c 100644 --- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx +++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx @@ -7,9 +7,16 @@ import React, { FC } from 'react'; -import { EuiSpacer, EuiInMemoryTable, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { + EuiBasicTableColumn, + EuiSpacer, + EuiInMemoryTable, + EuiButtonIcon, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { JobMessage } from '../../../../common/types/audit_message'; @@ -21,14 +28,21 @@ interface JobMessagesProps { loading: boolean; error: string; refreshMessage?: React.MouseEventHandler; + actionHandler?: (message: JobMessage) => void; } /** * Component for rendering job messages for anomaly detection * and data frame analytics jobs. */ -export const JobMessages: FC = ({ messages, loading, error, refreshMessage }) => { - const columns = [ +export const JobMessages: FC = ({ + messages, + loading, + error, + refreshMessage, + actionHandler, +}) => { + const columns: Array> = [ { name: refreshMessage ? ( = ({ messages, loading, error, re }, ]; + if (typeof actionHandler === 'function') { + columns.push({ + name: i18n.translate('xpack.ml.jobMessages.actionsLabel', { + defaultMessage: 'Actions', + }), + width: '10%', + actions: [ + { + render: (message: JobMessage) => { + return ( + + } + > + actionHandler(message)} + /> + + ); + }, + }, + ], + }); + } + const defaultSorting = { sort: { field: 'timestamp' as const, @@ -93,6 +141,7 @@ export const JobMessages: FC = ({ messages, loading, error, re compressed={true} loading={loading} error={error} + pagination={true} data-test-subj={'mlAnalyticsDetailsJobMessagesTable'} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/constants.ts similarity index 88% rename from x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts rename to x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/constants.ts index b3b9487523196..4b761da6ed894 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/constants.ts +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/constants.ts @@ -26,14 +26,14 @@ export type TabIdsType = typeof TAB_IDS[keyof typeof TAB_IDS]; export const tabs = [ { id: TAB_IDS.CHART, - name: i18n.translate('xpack.ml.jobsList.datafeedModal.chartTabName', { + name: i18n.translate('xpack.ml.jobsList.datafeedChart.chartTabName', { defaultMessage: 'Chart', }), disabled: false, }, { id: TAB_IDS.MESSAGES, - name: i18n.translate('xpack.ml.jobsList.datafeedModal.messagesTabName', { + name: i18n.translate('xpack.ml.jobsList.datafeedChart.messagesTabName', { defaultMessage: 'Messages', }), disabled: false, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx similarity index 69% rename from x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx rename to x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx index 2dece82e6f5c7..55ab8ceb67058 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/datafeed_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx @@ -15,15 +15,13 @@ import { EuiDatePicker, EuiFlexGroup, EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, EuiIcon, EuiIconTip, EuiLoadingChart, - EuiModal, - EuiModalHeader, - EuiModalBody, - EuiSpacer, - EuiTabs, - EuiTab, + EuiPortal, EuiText, EuiTitle, EuiToolTip, @@ -47,25 +45,19 @@ import { import { DATAFEED_STATE } from '../../../../../../common/constants/states'; import { CombinedJobWithStats } from '../../../../../../common/types/anomaly_detection_jobs'; +import { JobMessage } from '../../../../../../common/types/audit_message'; import { useToastNotificationService } from '../../../../services/toast_notification_service'; import { useMlApiContext } from '../../../../contexts/kibana'; import { useCurrentEuiTheme } from '../../../../components/color_range_legend'; import { JobMessagesPane } from '../job_details/job_messages_pane'; import { EditQueryDelay } from './edit_query_delay'; -import { - CHART_DIRECTION, - ChartDirectionType, - CHART_SIZE, - tabs, - TAB_IDS, - TabIdsType, -} from './constants'; +import { CHART_DIRECTION, ChartDirectionType, CHART_SIZE } from './constants'; import { loadFullJob } from '../utils'; const dateFormatter = timeFormatter('MM-DD HH:mm:ss'); const MAX_CHART_POINTS = 480; -interface DatafeedModalProps { +interface DatafeedChartFlyoutProps { jobId: string; end: number; onClose: () => void; @@ -76,14 +68,13 @@ function setLineAnnotationHeader(lineDatum: LineAnnotationDatum) { return lineDatum; } -export const DatafeedModal: FC = ({ jobId, end, onClose }) => { +export const DatafeedChartFlyout: FC = ({ jobId, end, onClose }) => { const [data, setData] = useState<{ datafeedConfig: CombinedJobWithStats['datafeed_config'] | undefined; bucketSpan: string | undefined; isInitialized: boolean; }>({ datafeedConfig: undefined, bucketSpan: undefined, isInitialized: false }); const [endDate, setEndDate] = useState(moment(end)); - const [selectedTabId, setSelectedTabId] = useState(TAB_IDS.CHART); const [isLoadingChartData, setIsLoadingChartData] = useState(false); const [bucketData, setBucketData] = useState([]); const [annotationData, setAnnotationData] = useState<{ @@ -91,9 +82,11 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = line: LineAnnotationDatum[]; }>({ rect: [], line: [] }); const [modelSnapshotData, setModelSnapshotData] = useState([]); + const [messageData, setMessageData] = useState([]); const [sourceData, setSourceData] = useState([]); const [showAnnotations, setShowAnnotations] = useState(true); const [showModelSnapshots, setShowModelSnapshots] = useState(true); + const [range, setRange] = useState<{ start: string; end: string } | undefined>(); const { results: { getDatafeedResultChartData }, @@ -101,25 +94,6 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = const { displayErrorToast } = useToastNotificationService(); const { euiTheme } = useCurrentEuiTheme(); - const onSelectedTabChanged = (id: TabIdsType) => { - setSelectedTabId(id); - }; - - const renderTabs = useCallback( - () => - tabs.map((tab, index) => ( - onSelectedTabChanged(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - key={index} - > - {tab.name} - - )), - [selectedTabId] - ); - const handleChange = (date: moment.Moment) => setEndDate(date); const handleEndDateChange = (direction: ChartDirectionType) => { @@ -148,6 +122,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = // STARTTIME = ENDTIME - (BucketSpan * MAX_CHART_POINTS) const startMoment = endDate.clone().subtract(MAX_CHART_POINTS * count, unit); const startTimestamp = moment(startMoment).valueOf(); + setRange({ start: String(startTimestamp), end: String(endTimestamp) }); try { const chartData = await getDatafeedResultChartData(jobId, startTimestamp, endTimestamp); @@ -160,7 +135,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = }); setModelSnapshotData(chartData.modelSnapshotResultsLine.map(setLineAnnotationHeader)); } catch (error) { - const title = i18n.translate('xpack.ml.jobsList.datafeedModal.errorToastTitle', { + const title = i18n.translate('xpack.ml.jobsList.datafeedChart.errorToastTitle', { defaultMessage: 'Error fetching data', }); displayErrorToast(error, title); @@ -168,7 +143,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = setIsLoadingChartData(false); }, [endDate, data.bucketSpan]); - const getJobData = async () => { + const getJobData = useCallback(async () => { try { const job: CombinedJobWithStats = await loadFullJob(jobId); setData({ @@ -179,7 +154,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = } catch (error) { displayErrorToast(error); } - }; + }, [jobId]); useEffect(function loadJobWithDatafeed() { getJobData(); @@ -200,65 +175,62 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = const checkboxIdModelSnapshot = useMemo(() => htmlIdGenerator()(), []); return ( - - - - - - - - } - /> - - - -

- -

- - - - - - - - - - - - {renderTabs()} - - {isLoadingChartData || isInitialized === false ? : null} - {!isLoadingChartData && - isInitialized && - selectedTabId === TAB_IDS.CHART && - datafeedConfig !== undefined && - bucketSpan && ( + + + + + + + + + } + /> + + + +

+ +

+
+
+
+
+ + + +
+
+ + {isLoadingChartData || isInitialized === false ? : null} + {!isLoadingChartData && isInitialized && datafeedConfig !== undefined && bucketSpan ? ( @@ -277,7 +249,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = label={ @@ -292,7 +264,7 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = label={ @@ -311,14 +283,14 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = } > = ({ jobId, end, onClose }) = position={Position.Bottom} showOverlappingTicks tickFormat={dateFormatter} - title={i18n.translate('xpack.ml.jobsList.datafeedModal.xAxisTitle', { + title={i18n.translate('xpack.ml.jobsList.datafeedChart.xAxisTitle', { defaultMessage: 'Bucket span ({bucketSpan})', values: { bucketSpan }, })} /> = ({ jobId, end, onClose }) = {showModelSnapshots ? ( = ({ jobId, end, onClose }) = <> = ({ jobId, end, onClose }) = key="annotation-results-rect" dataValues={annotationData.rect} id={i18n.translate( - 'xpack.ml.jobsList.datafeedModal.annotationRectSeriesId', + 'xpack.ml.jobsList.datafeedChart.annotationRectSeriesId', { defaultMessage: 'Annotations rectangle result', } @@ -419,10 +391,34 @@ export const DatafeedModal: FC = ({ jobId, end, onClose }) = /> ) : null} + {messageData.length > 0 ? ( + <> + } + markerPosition={Position.Top} + style={{ + line: { + strokeWidth: 3, + stroke: euiTheme.euiColorAccent, + opacity: 0.5, + }, + }} + /> + + ) : null} = ({ jobId, end, onClose }) = = ({ jobId, end, onClose }) = } > = ({ jobId, end, onClose }) = + {range !== undefined ? ( + + + +

+ +

+
+ 0 && + messageData[0].dataValue === message.timestamp + ) { + setMessageData([]); + } else { + const datum = setLineAnnotationHeader({ + dataValue: message.timestamp, + details: message.message, + }); + + setMessageData([datum]); + } + }} + /> +
+
+ ) : null} - )} - {!isLoadingChartData && selectedTabId === TAB_IDS.MESSAGES ? ( - - ) : null} -
- + ) : null} + + + ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/edit_query_delay.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/edit_query_delay.tsx similarity index 90% rename from x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/edit_query_delay.tsx rename to x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/edit_query_delay.tsx index 5a25781a505d1..feda98779c4d9 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/edit_query_delay.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/edit_query_delay.tsx @@ -22,7 +22,7 @@ import { useToastNotificationService } from '../../../../services/toast_notifica import { Datafeed } from '../../../../../../common/types/anomaly_detection_jobs'; const tooltipContent = i18n.translate( - 'xpack.ml.jobsList.datafeedModal.editQueryDelay.tooltipContent', + 'xpack.ml.jobsList.datafeedChart.editQueryDelay.tooltipContent', { defaultMessage: 'Cannot update query delay when datafeed is running.', } @@ -46,7 +46,7 @@ export const EditQueryDelay: FC<{ }); displaySuccessToast( i18n.translate( - 'xpack.ml.jobsList.datafeedModal.editQueryDelay.changesSavedNotificationMessage', + 'xpack.ml.jobsList.datafeedChart.editQueryDelay.changesSavedNotificationMessage', { defaultMessage: 'Changes to query delay for {datafeedId} saved', values: { @@ -59,7 +59,7 @@ export const EditQueryDelay: FC<{ displayErrorToast( error, i18n.translate( - 'xpack.ml.jobsList.datafeedModal.editQueryDelay.changesNotSavedNotificationMessage', + 'xpack.ml.jobsList.datafeedChart.editQueryDelay.changesNotSavedNotificationMessage', { defaultMessage: 'Could not save changes to query delay for {datafeedId}', values: { @@ -83,7 +83,7 @@ export const EditQueryDelay: FC<{ iconType="pencil" > @@ -99,7 +99,7 @@ export const EditQueryDelay: FC<{ } @@ -119,7 +119,7 @@ export const EditQueryDelay: FC<{ @@ -127,7 +127,7 @@ export const EditQueryDelay: FC<{ setIsEditing(false)}> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/index.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/index.ts similarity index 80% rename from x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/index.ts rename to x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/index.ts index 68c50c6663e1d..ba1b6ae0b9707 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_modal/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { DatafeedModal } from './datafeed_modal'; +export { DatafeedChartFlyout } from './datafeed_chart_flyout'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index d3856e6afa398..d030fe08eef3e 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -15,7 +15,7 @@ import { extractJobDetails } from './extract_job_details'; import { JsonPane } from './json_tab'; import { DatafeedPreviewPane } from './datafeed_preview_tab'; import { AnnotationsTable } from '../../../../components/annotations/annotations_table'; -import { DatafeedModal } from '../datafeed_modal'; +import { DatafeedChartFlyout } from '../datafeed_chart_flyout'; import { AnnotationFlyout } from '../../../../components/annotations/annotation_flyout'; import { ModelSnapshotTable } from '../../../../components/model_snapshots'; import { ForecastsTable } from './forecasts_table'; @@ -28,7 +28,7 @@ export class JobDetailsUI extends Component { super(props); this.state = { - datafeedModalVisible: false, + datafeedChartFlyoutVisible: false, }; if (this.props.addYourself) { this.props.addYourself(props.jobId, (j) => this.updateJob(j)); @@ -97,7 +97,7 @@ export class JobDetailsUI extends Component { iconType="visAreaStacked" onClick={() => this.setState({ - datafeedModalVisible: true, + datafeedChartFlyoutVisible: true, }) } /> @@ -144,11 +144,11 @@ export class JobDetailsUI extends Component { data-test-subj="mlJobDetails-datafeed" sections={[datafeed, datafeedTimingStats]} /> - {this.props.jobId && this.state.datafeedModalVisible ? ( - { this.setState({ - datafeedModalVisible: false, + datafeedChartFlyoutVisible: false, }); }} end={job.data_counts.latest_bucket_timestamp} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx index f2b6fb03b2961..f808d512e1dfa 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx @@ -14,44 +14,50 @@ import { extractErrorMessage } from '../../../../../../common/util/errors'; import { useToastNotificationService } from '../../../../services/toast_notification_service'; interface JobMessagesPaneProps { jobId: string; + start?: string; + end?: string; + actionHandler?: (message: JobMessage) => void; } -export const JobMessagesPane: FC = ({ jobId }) => { - const [messages, setMessages] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - const toastNotificationService = useToastNotificationService(); +export const JobMessagesPane: FC = React.memo( + ({ jobId, start, end, actionHandler }) => { + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const toastNotificationService = useToastNotificationService(); - const fetchMessages = async () => { - setIsLoading(true); - try { - setMessages(await ml.jobs.jobAuditMessages(jobId)); - setIsLoading(false); - } catch (error) { - setIsLoading(false); - toastNotificationService.displayErrorToast( - error, - i18n.translate('xpack.ml.jobService.jobAuditMessagesErrorTitle', { - defaultMessage: 'Error loading job messages', - }) - ); + const fetchMessages = async () => { + setIsLoading(true); + try { + setMessages(await ml.jobs.jobAuditMessages({ jobId, start, end })); + setIsLoading(false); + } catch (error) { + setIsLoading(false); + toastNotificationService.displayErrorToast( + error, + i18n.translate('xpack.ml.jobService.jobAuditMessagesErrorTitle', { + defaultMessage: 'Error loading job messages', + }) + ); - setErrorMessage(extractErrorMessage(error)); - } - }; + setErrorMessage(extractErrorMessage(error)); + } + }; - const refreshMessage = useCallback(fetchMessages, [jobId]); + const refreshMessage = useCallback(fetchMessages, [jobId]); - useEffect(() => { - fetchMessages(); - }, []); + useEffect(() => { + fetchMessages(); + }, []); - return ( - - ); -}; + return ( + + ); + } +); diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 7cd08bc1fd15c..1de7cb455cc2c 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -136,9 +136,23 @@ export const jobsApiProvider = (httpService: HttpService) => ({ }); }, - jobAuditMessages(jobId: string, from?: number) { + jobAuditMessages({ + jobId, + from, + start, + end, + }: { + jobId: string; + from?: number; + start?: string; + end?: string; + }) { const jobIdString = jobId !== undefined ? `/${jobId}` : ''; - const query = from !== undefined ? { from } : {}; + const query = { + ...(from !== undefined ? { from } : {}), + ...(start !== undefined && end !== undefined ? { start, end } : {}), + }; + return httpService.http({ path: `${ML_BASE_PATH}/job_audit_messages/messages${jobIdString}`, method: 'GET', diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts index 164152239bf1e..28287bfc8f1b4 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts @@ -15,8 +15,12 @@ export function jobAuditMessagesProvider( ): { getJobAuditMessages: ( jobSavedObjectService: JobSavedObjectService, - jobId?: string, - from?: string + options: { + jobId?: string; + from?: string; + start?: string; + end?: string; + } ) => any; getAuditMessagesSummary: (jobIds?: string[]) => any; }; diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js index 6125ba7eaa923..e349462d8421d 100644 --- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js +++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js @@ -39,7 +39,7 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) { // search for audit messages, // jobId is optional. without it, all jobs will be listed. // from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d - async function getJobAuditMessages(jobSavedObjectService, jobId, from) { + async function getJobAuditMessages(jobSavedObjectService, { jobId, from, start, end }) { let gte = null; if (jobId !== undefined && from === undefined) { const jobs = await mlClient.getJobs({ job_id: jobId }); @@ -62,6 +62,17 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) { }; } + if (start !== undefined && end !== undefined) { + timeFilter = { + range: { + timestamp: { + gte: start, + lte: end, + }, + }, + }; + } + const query = { bool: { filter: [ diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts index 36753294bdbe7..93d981aaa52af 100644 --- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts +++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts @@ -43,8 +43,13 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati try { const { getJobAuditMessages } = jobAuditMessagesProvider(client, mlClient); const { jobId } = request.params; - const { from } = request.query; - const resp = await getJobAuditMessages(jobSavedObjectService, jobId, from); + const { from, start, end } = request.query; + const resp = await getJobAuditMessages(jobSavedObjectService, { + jobId, + from, + start, + end, + }); return response.ok({ body: resp, @@ -80,7 +85,7 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati try { const { getJobAuditMessages } = jobAuditMessagesProvider(client, mlClient); const { from } = request.query; - const resp = await getJobAuditMessages(jobSavedObjectService, undefined, from); + const resp = await getJobAuditMessages(jobSavedObjectService, { from }); return response.ok({ body: resp, diff --git a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts index 8bd05e5b6d682..ef7e3afa47e9c 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts @@ -12,4 +12,8 @@ export const jobAuditMessagesJobIdSchema = schema.object({ jobId: schema.maybe(schema.string()), }); -export const jobAuditMessagesQuerySchema = schema.object({ from: schema.maybe(schema.string()) }); +export const jobAuditMessagesQuerySchema = schema.object({ + from: schema.maybe(schema.string()), + start: schema.maybe(schema.string()), + end: schema.maybe(schema.string()), +}); From 64df69890d28732cef730f1e02abf2b0f2f96c24 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Mon, 28 Jun 2021 13:31:48 -0600 Subject: [PATCH 17/74] [RAC] [Cases] Push to 3rd party UI updates (#103418) --- .../cases/public/common/translations.ts | 2 +- .../components/add_comment/index.test.tsx | 1 + .../public/components/add_comment/index.tsx | 40 +++- .../all_cases/all_cases_generic.tsx | 5 - .../public/components/all_cases/header.tsx | 2 +- .../components/all_cases/index.test.tsx | 2 +- .../components/all_cases/nav_buttons.tsx | 4 +- .../public/components/callout/callout.tsx | 52 ----- .../public/components/callout/index.test.tsx | 217 ------------------ .../public/components/callout/translations.ts | 12 - .../public/components/case_view/index.tsx | 80 ++----- .../components/edit_connector/index.test.tsx | 45 ++-- .../components/edit_connector/index.tsx | 82 ++++--- .../components/markdown_editor/editor.tsx | 7 +- .../callout/callout.test.tsx | 55 +++-- .../use_push_to_service/callout/callout.tsx | 64 ++++++ .../callout/helpers.test.tsx | 0 .../callout/helpers.tsx | 0 .../callout/index.test.tsx | 130 +++++++++++ .../callout/index.tsx | 55 ++--- .../callout/translations.ts | 25 ++ .../callout/types.ts | 6 +- .../use_push_to_service/helpers.tsx | 2 +- .../use_push_to_service/index.test.tsx | 10 +- .../components/use_push_to_service/index.tsx | 96 ++++---- .../use_push_to_service/translations.ts | 45 ++-- .../user_action_tree/index.test.tsx | 1 + .../components/user_action_tree/index.tsx | 5 +- .../plugins/cases/public/containers/mock.ts | 6 +- .../containers/use_update_case.test.tsx | 4 +- 30 files changed, 503 insertions(+), 552 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/callout/callout.tsx delete mode 100644 x-pack/plugins/cases/public/components/callout/index.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/callout/translations.ts rename x-pack/plugins/cases/public/components/{ => use_push_to_service}/callout/callout.test.tsx (64%) create mode 100644 x-pack/plugins/cases/public/components/use_push_to_service/callout/callout.tsx rename x-pack/plugins/cases/public/components/{ => use_push_to_service}/callout/helpers.test.tsx (100%) rename x-pack/plugins/cases/public/components/{ => use_push_to_service}/callout/helpers.tsx (100%) create mode 100644 x-pack/plugins/cases/public/components/use_push_to_service/callout/index.test.tsx rename x-pack/plugins/cases/public/components/{ => use_push_to_service}/callout/index.tsx (64%) create mode 100644 x-pack/plugins/cases/public/components/use_push_to_service/callout/translations.ts rename x-pack/plugins/cases/public/components/{ => use_push_to_service}/callout/types.ts (78%) diff --git a/x-pack/plugins/cases/public/common/translations.ts b/x-pack/plugins/cases/public/common/translations.ts index 43f94ecad2698..020d301c8e30e 100644 --- a/x-pack/plugins/cases/public/common/translations.ts +++ b/x-pack/plugins/cases/public/common/translations.ts @@ -161,7 +161,7 @@ export const SAVE = i18n.translate('xpack.cases.caseView.description.save', { }); export const CONNECTORS = i18n.translate('xpack.cases.caseView.connectors', { - defaultMessage: 'External Incident Management System', + defaultMessage: 'External incident management system', }); export const NO_CONNECTOR = i18n.translate('xpack.cases.common.noConnector', { diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index 078db1e6dbe6d..db3f22a074d3b 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -31,6 +31,7 @@ const addCommentProps: AddCommentProps = { onCommentSaving, onCommentPosted, showLoading: false, + statusActionButton: null, }; const defaultPostComment = { diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index 6604f3d2b8bc8..4ec06d6b55197 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiButton, EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui'; import React, { useCallback, forwardRef, useImperativeHandle } from 'react'; import styled from 'styled-components'; @@ -39,13 +39,22 @@ export interface AddCommentProps { onCommentSaving?: () => void; onCommentPosted: (newCase: Case) => void; showLoading?: boolean; + statusActionButton: JSX.Element | null; subCaseId?: string; } export const AddComment = React.memo( forwardRef( ( - { caseId, userCanCrud, onCommentPosted, onCommentSaving, showLoading = true, subCaseId }, + { + caseId, + userCanCrud, + onCommentPosted, + onCommentSaving, + showLoading = true, + statusActionButton, + subCaseId, + }, ref ) => { const owner = useOwnerContext(); @@ -102,16 +111,23 @@ export const AddComment = React.memo( dataTestSubj: 'add-comment', placeholder: i18n.ADD_COMMENT_HELP_TEXT, bottomRightContent: ( - - {i18n.ADD_COMMENT} - + + {statusActionButton && ( + {statusActionButton} + )} + + + {i18n.ADD_COMMENT} + + + ), }} /> diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx index a364f8bf2b068..308c2186b52ed 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx @@ -28,11 +28,9 @@ import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../common/translations'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { useGetCases } from '../../containers/use_get_cases'; import { usePostComment } from '../../containers/use_post_comment'; -import { CaseCallOut } from '../callout'; import { CaseDetailsHrefSchema, CasesNavigation } from '../links'; import { Panel } from '../panel'; import { getActionLicenseError } from '../use_push_to_service/helpers'; -import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations'; import { useCasesColumns } from './columns'; import { getExpandedRowMap } from './expanded_row'; import { CasesTableHeader } from './header'; @@ -267,9 +265,6 @@ export const AllCasesGeneric = React.memo( return ( <> - {!isEmpty(actionsErrors) && ( - - )} {configureCasesNavigation != null && ( { closedAt: null, closedBy: null, comments: [], - connector: { fields: null, id: '123', name: 'My Connector', type: '.none' }, + connector: { fields: null, id: 'none', name: 'My Connector', type: '.none' }, createdAt: '2020-02-19T23:06:33.798Z', createdBy: { email: 'leslie.knope@elastic.co', diff --git a/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx b/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx index b8755d03e0b00..ec83604987180 100644 --- a/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/nav_buttons.tsx @@ -11,7 +11,7 @@ import { isEmpty } from 'lodash/fp'; import { ConfigureCaseButton } from '../configure_cases/button'; import * as i18n from './translations'; import { CasesNavigation, LinkButton } from '../links'; -import { ErrorMessage } from '../callout/types'; +import { ErrorMessage } from '../use_push_to_service/callout/types'; interface OwnProps { actionsErrors: ErrorMessage[]; @@ -33,7 +33,7 @@ export const NavButtons: FunctionComponent = ({ label={i18n.CONFIGURE_CASES_BUTTON} isDisabled={!isEmpty(actionsErrors)} showToolTip={!isEmpty(actionsErrors)} - msgTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].description : <>} + msgTooltip={!isEmpty(actionsErrors) ? <>{actionsErrors[0].description} : <>} titleTooltip={!isEmpty(actionsErrors) ? actionsErrors[0].title : ''} /> diff --git a/x-pack/plugins/cases/public/components/callout/callout.tsx b/x-pack/plugins/cases/public/components/callout/callout.tsx deleted file mode 100644 index 4cd7fad10fe70..0000000000000 --- a/x-pack/plugins/cases/public/components/callout/callout.tsx +++ /dev/null @@ -1,52 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiCallOut, EuiButton, EuiDescriptionList } from '@elastic/eui'; -import { isEmpty } from 'lodash/fp'; -import React, { memo, useCallback } from 'react'; - -import { ErrorMessage } from './types'; -import * as i18n from './translations'; - -export interface CallOutProps { - id: string; - type: NonNullable; - title: string; - messages: ErrorMessage[]; - showCallOut: boolean; - handleDismissCallout: (id: string, type: NonNullable) => void; -} - -const CallOutComponent = ({ - id, - type, - title, - messages, - showCallOut, - handleDismissCallout, -}: CallOutProps) => { - const handleCallOut = useCallback(() => handleDismissCallout(id, type), [ - handleDismissCallout, - id, - type, - ]); - - return showCallOut && !isEmpty(messages) ? ( - - - - {i18n.DISMISS_CALLOUT} - - - ) : null; -}; - -export const CallOut = memo(CallOutComponent); diff --git a/x-pack/plugins/cases/public/components/callout/index.test.tsx b/x-pack/plugins/cases/public/components/callout/index.test.tsx deleted file mode 100644 index c46ec1b5606c9..0000000000000 --- a/x-pack/plugins/cases/public/components/callout/index.test.tsx +++ /dev/null @@ -1,217 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import { useMessagesStorage } from '../../containers/use_messages_storage'; -import { TestProviders } from '../../common/mock'; -import { createCalloutId } from './helpers'; -import { CaseCallOut, CaseCallOutProps } from '.'; - -jest.mock('../../containers/use_messages_storage'); - -const useSecurityLocalStorageMock = useMessagesStorage as jest.Mock; -const securityLocalStorageMock = { - getMessages: jest.fn(() => []), - addMessage: jest.fn(), -}; - -describe('CaseCallOut ', () => { - beforeEach(() => { - jest.clearAllMocks(); - useSecurityLocalStorageMock.mockImplementation(() => securityLocalStorageMock); - }); - - it('renders a callout correctly', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { id: 'message-one', title: 'title', description:

{'we have two messages'}

}, - { id: 'message-two', title: 'title', description:

{'for real'}

}, - ], - }; - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one', 'message-two']); - expect(wrapper.find(`[data-test-subj="callout-messages-${id}"]`).last().exists()).toBeTruthy(); - }); - - it('groups the messages correctly', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { - id: 'message-one', - title: 'title one', - description:

{'we have two messages'}

, - errorType: 'danger', - }, - { id: 'message-two', title: 'title two', description:

{'for real'}

}, - ], - }; - - const wrapper = mount( - - - - ); - - const idDanger = createCalloutId(['message-one']); - const idPrimary = createCalloutId(['message-two']); - - expect( - wrapper.find(`[data-test-subj="case-callout-${idPrimary}"]`).last().exists() - ).toBeTruthy(); - expect( - wrapper.find(`[data-test-subj="case-callout-${idDanger}"]`).last().exists() - ).toBeTruthy(); - }); - - it('dismisses the callout correctly', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { id: 'message-one', title: 'title', description:

{'we have two messages'}

}, - ], - }; - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one']); - - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeTruthy(); - wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).exists()).toBeFalsy(); - }); - - it('persist the callout of type primary when dismissed', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { id: 'message-one', title: 'title', description:

{'we have two messages'}

}, - ], - }; - - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one']); - expect(securityLocalStorageMock.getMessages).toHaveBeenCalledWith('case'); - wrapper.find(`[data-test-subj="callout-dismiss-${id}"]`).last().simulate('click'); - expect(securityLocalStorageMock.addMessage).toHaveBeenCalledWith('case', id); - }); - - it('do not show the callout if is in the localStorage', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { id: 'message-one', title: 'title', description:

{'we have two messages'}

}, - ], - }; - - const id = createCalloutId(['message-one']); - - useSecurityLocalStorageMock.mockImplementation(() => ({ - ...securityLocalStorageMock, - getMessages: jest.fn(() => [id]), - })); - - const wrapper = mount( - - - - ); - - expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeFalsy(); - }); - - it('do not persist a callout of type danger', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { - id: 'message-one', - title: 'title one', - description:

{'we have two messages'}

, - errorType: 'danger', - }, - ], - }; - - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); - wrapper.update(); - expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); - }); - - it('do not persist a callout of type warning', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { - id: 'message-one', - title: 'title one', - description:

{'we have two messages'}

, - errorType: 'warning', - }, - ], - }; - - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); - wrapper.update(); - expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); - }); - - it('do not persist a callout of type success', () => { - const props: CaseCallOutProps = { - title: 'hey title', - messages: [ - { - id: 'message-one', - title: 'title one', - description:

{'we have two messages'}

, - errorType: 'success', - }, - ], - }; - - const wrapper = mount( - - - - ); - - const id = createCalloutId(['message-one']); - wrapper.find(`button[data-test-subj="callout-dismiss-${id}"]`).simulate('click'); - wrapper.update(); - expect(securityLocalStorageMock.addMessage).not.toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/callout/translations.ts b/x-pack/plugins/cases/public/components/callout/translations.ts deleted file mode 100644 index 8b0ad31dba88e..0000000000000 --- a/x-pack/plugins/cases/public/components/callout/translations.ts +++ /dev/null @@ -1,12 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const DISMISS_CALLOUT = i18n.translate('xpack.cases.dismissErrorsPushServiceCallOutTitle', { - defaultMessage: 'Dismiss', -}); diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index d5b535b8ddad1..0e49173a6b838 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -7,14 +7,7 @@ import React, { useCallback, useEffect, useMemo, useState, useRef, MutableRefObject } from 'react'; import styled from 'styled-components'; -import { isEmpty } from 'lodash/fp'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiLoadingContent, - EuiLoadingSpinner, - EuiHorizontalRule, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner } from '@elastic/eui'; import { CaseStatuses, @@ -36,7 +29,6 @@ import { getTypedPayload } from '../../containers/utils'; import { WhitePageWrapper, HeaderWrapper } from '../wrappers'; import { CaseActionBar } from '../case_action_bar'; import { useGetCaseUserActions } from '../../containers/use_get_case_user_actions'; -import { usePushToService } from '../use_push_to_service'; import { EditConnector } from '../edit_connector'; import { useConnectors } from '../../containers/configure/use_connectors'; import { normalizeActionConnector, getNoneConnector } from '../configure_cases/utils'; @@ -91,14 +83,6 @@ const MyEuiFlexGroup = styled(EuiFlexGroup)` height: 100%; `; -const MyEuiHorizontalRule = styled(EuiHorizontalRule)` - margin-left: 48px; - - &.euiHorizontalRule--full { - width: calc(100% - 48px); - } -`; - export interface CaseComponentProps extends CaseViewComponentProps { fetchCase: UseGetCase['fetchCase']; caseData: Case; @@ -306,21 +290,6 @@ export const CaseComponent = React.memo( [caseServices, caseData.connector] ); - const { pushButton, pushCallouts } = usePushToService({ - configureCasesNavigation, - connector: { - ...caseData.connector, - name: isEmpty(connectorName) ? caseData.connector.name : connectorName, - }, - caseServices, - caseId: caseData.id, - caseStatus: caseData.status, - connectors, - updateCase: handleUpdateCase, - userCanCrud, - isValidConnector: isLoadingConnectors ? true : isValidConnector, - }); - const onSubmitConnector = useCallback( (connectorId, connectorFields, onError, onSuccess) => { const connector = getConnectorById(connectorId, connectors); @@ -434,7 +403,6 @@ export const CaseComponent = React.memo( - {!initLoadingData && pushCallouts != null && pushCallouts} {initLoadingData && ( @@ -463,34 +431,19 @@ export const CaseComponent = React.memo( renderInvestigateInTimelineActionComponent={ timelineUi?.renderInvestigateInTimelineActionComponent } + statusActionButton={ + caseData.type !== CaseType.collection && userCanCrud ? ( + + ) : null + } updateCase={updateCase} useFetchAlertData={useFetchAlertData} userCanCrud={userCanCrud} /> - {(caseData.type !== CaseType.collection || hasDataToPush) && userCanCrud && ( - <> - - - {caseData.type !== CaseType.collection && ( - - - - )} - {hasDataToPush && ( - - {pushButton} - - )} - - - )} )} @@ -516,17 +469,22 @@ export const CaseComponent = React.memo( isLoading={isLoading && updateKey === 'tags'} />
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx index 33efb7e447583..29d64afe3284f 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx @@ -13,7 +13,7 @@ import { EditConnector, EditConnectorProps } from './index'; import { getFormMock, useFormMock } from '../__mock__/form'; import { TestProviders } from '../../common/mock'; import { connectorsMock } from '../../containers/configure/mock'; -import { caseUserActions } from '../../containers/mock'; +import { basicCase, basicPush, caseUserActions } from '../../containers/mock'; import { useKibana } from '../../common/lib/kibana'; jest.mock('../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'); @@ -21,14 +21,32 @@ jest.mock('../../common/lib/kibana'); const useKibanaMock = useKibana as jest.Mocked; const onSubmit = jest.fn(); +const updateCase = jest.fn(); +const caseServices = { + '123': { + ...basicPush, + firstPushIndex: 0, + lastPushIndex: 0, + commentsToUpdate: [], + hasDataToPush: true, + }, +}; const defaultProps: EditConnectorProps = { + caseData: basicCase, + caseServices, + configureCasesNavigation: { + href: 'blah', + onClick: jest.fn(), + }, + connectorName: connectorsMock[0].name, connectors: connectorsMock, - userCanCrud: true, + hasDataToPush: true, isLoading: false, + isValidConnector: true, onSubmit, - selectedConnector: 'none', - caseFields: null, + updateCase, userActions: caseUserActions, + userCanCrud: true, }; describe('EditConnector ', () => { @@ -128,7 +146,7 @@ describe('EditConnector ', () => { wrapper.update(); expect(formHookMock.setFieldValue).toBeCalledWith( 'connectorId', - defaultProps.selectedConnector + defaultProps.caseData.connector.id ); }); }); @@ -176,21 +194,20 @@ describe('EditConnector ', () => { }); }); - it('displays the default none connector message', async () => { - const props = { ...defaultProps }; + it('displays the callout message when none is selected', async () => { + const props = { ...defaultProps, connectors: [] }; const wrapper = mount( ); - + wrapper.update(); await waitFor(() => { - expect( - wrapper.find(`[data-test-subj="edit-connector-permissions-error-msg"]`).exists() - ).toBeFalsy(); - expect( - wrapper.find(`[data-test-subj="edit-connector-no-connectors-msg"]`).exists() - ).toBeTruthy(); + expect(true).toBeTruthy(); + }); + wrapper.update(); + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="push-callouts"]`).exists()).toEqual(true); }); }); }); diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx index 8057d188b8c04..fe92bd28ce21c 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx @@ -18,10 +18,10 @@ import { EuiButtonIcon, } from '@elastic/eui'; import styled from 'styled-components'; -import { noop } from 'lodash/fp'; +import { isEmpty, noop } from 'lodash/fp'; import { FieldConfig, Form, UseField, useForm } from '../../common/shared_imports'; -import { ActionConnector, ConnectorTypeFields } from '../../../common'; +import { ActionConnector, Case, ConnectorTypeFields } from '../../../common'; import { ConnectorSelector } from '../connector_selector/form'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { CaseUserActions } from '../../containers/types'; @@ -29,22 +29,30 @@ import { schema } from './schema'; import { getConnectorFieldsFromUserActions } from './helpers'; import * as i18n from './translations'; import { getConnectorById, getConnectorsFormValidators } from '../utils'; +import { usePushToService } from '../use_push_to_service'; +import { CasesNavigation } from '../links'; +import { CaseServices } from '../../containers/use_get_case_user_actions'; export interface EditConnectorProps { - caseFields: ConnectorTypeFields['fields']; + caseData: Case; + caseServices: CaseServices; + configureCasesNavigation: CasesNavigation; + connectorName: string; connectors: ActionConnector[]; + hasDataToPush: boolean; + hideConnectorServiceNowSir?: boolean; isLoading: boolean; + isValidConnector: boolean; onSubmit: ( connectorId: string, connectorFields: ConnectorTypeFields['fields'], onError: () => void, onSuccess: () => void ) => void; - selectedConnector: string; + permissionsError?: string; + updateCase: (newCase: Case) => void; userActions: CaseUserActions[]; userCanCrud?: boolean; - hideConnectorServiceNowSir?: boolean; - permissionsError?: string; } const MyFlexGroup = styled(EuiFlexGroup)` @@ -103,16 +111,23 @@ const initialState = { export const EditConnector = React.memo( ({ - caseFields, + caseData, + caseServices, + configureCasesNavigation, + connectorName, connectors, - userCanCrud = true, + hasDataToPush, hideConnectorServiceNowSir = false, isLoading, + isValidConnector, onSubmit, - selectedConnector, - userActions, permissionsError, + updateCase, + userActions, + userCanCrud = true, }: EditConnectorProps) => { + const caseFields = caseData.connector.fields; + const selectedConnector = caseData.connector.id; const { form } = useForm({ defaultValue: { connectorId: selectedConnector }, options: { stripEmptyFields: false }, @@ -210,17 +225,22 @@ export const EditConnector = React.memo( connectors, }); - /** - * if this evaluates to true it means that the connector was likely deleted because the case connector was set to something - * other than none but we don't find it in the list of connectors returned from the actions plugin - */ - const connectorFromCaseMissing = currentConnector == null && selectedConnector !== 'none'; - - /** - * True if the chosen connector from the form was the "none" connector or no connector was in the case. The - * currentConnector will be null initially and after the form initializes if the case connector is "none" - */ - const connectorUndefinedOrNone = currentConnector == null || currentConnector?.id === 'none'; + const { pushButton, pushCallouts } = usePushToService({ + configureCasesNavigation, + connector: { + ...caseData.connector, + name: isEmpty(connectorName) ? caseData.connector.name : connectorName, + }, + caseServices, + caseId: caseData.id, + caseStatus: caseData.status, + connectors, + hasDataToPush, + onEditClick, + updateCase, + userCanCrud, + isValidConnector, + }); return ( @@ -242,6 +262,9 @@ export const EditConnector = React.memo( + {!isLoading && !editConnector && pushCallouts && permissionsError == null && ( + {pushCallouts} + )} @@ -267,20 +290,10 @@ export const EditConnector = React.memo( - {!editConnector && permissionsError ? ( + {!editConnector && permissionsError && ( {permissionsError} - ) : ( - // if we're not editing the connectors and the connector specified in the case was found and the connector - // is undefined or explicitly set to none - !editConnector && - !connectorFromCaseMissing && - connectorUndefinedOrNone && ( - - {i18n.NO_CONNECTOR} - - ) )} )} + {pushCallouts == null && !isLoading && !editConnector && ( + + {pushButton} + + )} ); diff --git a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx index 3d9e75c6450d0..4bd26678e41a2 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/editor.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useEffect, useState, useCallback } from 'react'; +import React, { memo, useState, useCallback } from 'react'; import { PluggableList } from 'unified'; import { EuiMarkdownEditor, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { usePlugins } from './use_plugins'; @@ -36,11 +36,6 @@ const MarkdownEditorComponent: React.FC = ({ }, []); const { parsingPlugins, processingPlugins, uiPlugins } = usePlugins(); - useEffect( - () => document.querySelector('textarea.euiMarkdownEditorTextArea')?.focus(), - [] - ); - return ( { + const handleButtonClick = jest.fn(); const defaultProps: CallOutProps = { id: 'md5-hex', type: 'primary', - title: 'a tittle', messages: [ { id: 'generic-error', @@ -22,8 +24,7 @@ describe('Callout', () => { description:

{'error'}

, }, ], - showCallOut: true, - handleDismissCallout: jest.fn(), + handleButtonClick, }; beforeEach(() => { @@ -34,12 +35,7 @@ describe('Callout', () => { const wrapper = mount(); expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="callout-messages-md5-hex"]`).exists()).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); - }); - - it('hides the callout', () => { - const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="case-callout-md5-hex"]`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="callout-onclick-md5-hex"]`).exists()).toBeTruthy(); }); it('does not shows any messages when the list is empty', () => { @@ -50,7 +46,7 @@ describe('Callout', () => { it('transform the button color correctly - primary', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--primary')).toBeTruthy(); }); @@ -58,7 +54,7 @@ describe('Callout', () => { it('transform the button color correctly - success', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--secondary')).toBeTruthy(); }); @@ -66,7 +62,7 @@ describe('Callout', () => { it('transform the button color correctly - warning', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--warning')).toBeTruthy(); }); @@ -74,17 +70,38 @@ describe('Callout', () => { it('transform the button color correctly - danger', () => { const wrapper = mount(); const className = - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ?? + wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).first().prop('className') ?? ''; expect(className.includes('euiButton--danger')).toBeTruthy(); }); - it('dismiss the callout correctly', () => { - const wrapper = mount(); - expect(wrapper.find(`[data-test-subj="callout-dismiss-md5-hex"]`).exists()).toBeTruthy(); - wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).simulate('click'); - wrapper.update(); + it('does not show the button when case is closed error is present', () => { + const props = { + ...defaultProps, + messages: [ + { + id: CLOSED_CASE_PUSH_ERROR_ID, + title: 'message-one', + description:

{'error'}

, + }, + ], + }; + const wrapper = mount( + + + + ); + expect(wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).exists()).toEqual( + false + ); + }); - expect(defaultProps.handleDismissCallout).toHaveBeenCalledWith('md5-hex', 'primary'); + // use this for storage if we ever want to bring that back + it('onClick passes id and type', () => { + const wrapper = mount(); + expect(wrapper.find(`[data-test-subj="callout-onclick-md5-hex"]`).exists()).toBeTruthy(); + wrapper.find(`button[data-test-subj="callout-onclick-md5-hex"]`).simulate('click'); + expect(handleButtonClick.mock.calls[0][1]).toEqual('md5-hex'); + expect(handleButtonClick.mock.calls[0][2]).toEqual('primary'); }); }); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/callout/callout.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/callout.tsx new file mode 100644 index 0000000000000..e7018b52ef040 --- /dev/null +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/callout.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut, EuiButton, EuiDescriptionList } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { memo, useCallback, useMemo } from 'react'; + +import { CLOSED_CASE_PUSH_ERROR_ID, ErrorMessage } from './types'; +import * as i18n from './translations'; + +export interface CallOutProps { + handleButtonClick: ( + e: React.MouseEvent, + id: string, + type: NonNullable + ) => void; + id: string; + messages: ErrorMessage[]; + type: NonNullable; +} + +const CallOutComponent = ({ handleButtonClick, id, messages, type }: CallOutProps) => { + const handleCallOut = useCallback((e) => handleButtonClick(e, id, type), [ + handleButtonClick, + id, + type, + ]); + + const isCaseClosed = useMemo( + () => messages.map((m) => m.id).includes(CLOSED_CASE_PUSH_ERROR_ID), + [messages] + ); + + return !isEmpty(messages) ? ( + + + {!isCaseClosed && ( + + {i18n.ADD_CONNECTOR} + + )} + + ) : null; +}; + +export const CallOut = memo(CallOutComponent); diff --git a/x-pack/plugins/cases/public/components/callout/helpers.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/helpers.test.tsx similarity index 100% rename from x-pack/plugins/cases/public/components/callout/helpers.test.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/callout/helpers.test.tsx diff --git a/x-pack/plugins/cases/public/components/callout/helpers.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/helpers.tsx similarity index 100% rename from x-pack/plugins/cases/public/components/callout/helpers.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/callout/helpers.tsx diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.test.tsx new file mode 100644 index 0000000000000..678db2db06a3e --- /dev/null +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.test.tsx @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { useMessagesStorage } from '../../../containers/use_messages_storage'; +import { TestProviders } from '../../../common/mock'; +import { createCalloutId } from './helpers'; +import { CaseCallOut, CaseCallOutProps } from '.'; + +jest.mock('../../../containers/use_messages_storage'); + +const useSecurityLocalStorageMock = useMessagesStorage as jest.Mock; +const securityLocalStorageMock = { + getMessages: jest.fn(() => []), + addMessage: jest.fn(), +}; + +describe('CaseCallOut ', () => { + beforeEach(() => { + jest.clearAllMocks(); + useSecurityLocalStorageMock.mockImplementation(() => securityLocalStorageMock); + }); + const defaultProps: CaseCallOutProps = { + configureCasesNavigation: { + href: 'testHref', + onClick: jest.fn(), + }, + hasConnectors: true, + messages: [ + { id: 'message-one', title: 'title', description:

{'we have two messages'}

}, + { id: 'message-two', title: 'title', description:

{'for real'}

}, + ], + onEditClick: jest.fn(), + }; + + it('renders a callout correctly', () => { + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one', 'message-two']); + expect(wrapper.find(`[data-test-subj="callout-messages-${id}"]`).last().exists()).toBeTruthy(); + }); + + it('groups the messages correctly', () => { + const props: CaseCallOutProps = { + ...defaultProps, + messages: [ + { + id: 'message-one', + title: 'title one', + description:

{'we have two messages'}

, + errorType: 'danger', + }, + { id: 'message-two', title: 'title two', description:

{'for real'}

}, + ], + }; + + const wrapper = mount( + + + + ); + + const idDanger = createCalloutId(['message-one']); + const idPrimary = createCalloutId(['message-two']); + + expect( + wrapper.find(`[data-test-subj="case-callout-${idPrimary}"]`).last().exists() + ).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="case-callout-${idDanger}"]`).last().exists() + ).toBeTruthy(); + }); + + it('Opens edit connectors when hasConnectors=true', () => { + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one', 'message-two']); + wrapper.find(`[data-test-subj="callout-onclick-${id}"]`).last().simulate('click'); + expect(defaultProps.onEditClick).toHaveBeenCalled(); + expect(defaultProps.configureCasesNavigation.onClick).not.toHaveBeenCalled(); + }); + + it('Redirects to configure page when hasConnectors=false', () => { + const props = { + ...defaultProps, + hasConnectors: false, + }; + const wrapper = mount( + + + + ); + + const id = createCalloutId(['message-one', 'message-two']); + wrapper.find(`[data-test-subj="callout-onclick-${id}"]`).last().simulate('click'); + expect(defaultProps.onEditClick).not.toHaveBeenCalled(); + expect(defaultProps.configureCasesNavigation.onClick).toHaveBeenCalled(); + }); + + it('do not show the callout if is in the localStorage', () => { + const id = createCalloutId(['message-one']); + + useSecurityLocalStorageMock.mockImplementation(() => ({ + ...securityLocalStorageMock, + getMessages: jest.fn(() => [id]), + })); + + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-callout-${id}"]`).last().exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/callout/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx similarity index 64% rename from x-pack/plugins/cases/public/components/callout/index.tsx rename to x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx index 1994617d62801..036d3bdda7145 100644 --- a/x-pack/plugins/cases/public/components/callout/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx @@ -6,18 +6,20 @@ */ import { EuiSpacer } from '@elastic/eui'; -import React, { memo, useCallback, useState, useMemo } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; -import { useMessagesStorage } from '../../containers/use_messages_storage'; import { CallOut } from './callout'; import { ErrorMessage } from './types'; import { createCalloutId } from './helpers'; +import { CasesNavigation } from '../../links'; export * from './helpers'; export interface CaseCallOutProps { - title: string; + configureCasesNavigation: CasesNavigation; + hasConnectors: boolean; messages?: ErrorMessage[]; + onEditClick: () => void; } type GroupByTypeMessages = { @@ -26,36 +28,23 @@ type GroupByTypeMessages = { messages: ErrorMessage[]; }; }; - -interface CalloutVisibility { - [index: string]: boolean; -} - -const CaseCallOutComponent = ({ title, messages = [] }: CaseCallOutProps) => { - const { getMessages, addMessage } = useMessagesStorage(); - - const caseMessages = useMemo(() => getMessages('case'), [getMessages]); - const dismissedCallouts = useMemo( - () => - caseMessages.reduce( - (acc, id) => ({ - ...acc, - [id]: false, - }), - {} - ), - [caseMessages] - ); - - const [calloutVisibility, setCalloutVisibility] = useState(dismissedCallouts); +const CaseCallOutComponent = ({ + configureCasesNavigation, + hasConnectors, + onEditClick, + messages = [], +}: CaseCallOutProps) => { const handleCallOut = useCallback( - (id, type) => { - setCalloutVisibility((prevState) => ({ ...prevState, [id]: false })); - if (type === 'primary') { - addMessage('case', id); + (e) => { + // if theres connectors open dropdown editor + // if no connectors, redirect to create case page + if (hasConnectors) { + onEditClick(); + } else { + configureCasesNavigation.onClick(e); } }, - [setCalloutVisibility, addMessage] + [hasConnectors, onEditClick, configureCasesNavigation] ); const groupedByTypeErrorMessages = useMemo( @@ -84,12 +73,10 @@ const CaseCallOutComponent = ({ title, messages = [] }: CaseCallOutProps) => { return ( diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/callout/translations.ts b/x-pack/plugins/cases/public/components/use_push_to_service/callout/translations.ts new file mode 100644 index 0000000000000..8db1619e85a82 --- /dev/null +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/translations.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const ADD_CONNECTOR = i18n.translate('xpack.cases.addConnector.title', { + defaultMessage: 'Add connector', +}); + +export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( + 'xpack.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle', + { + defaultMessage: 'Reopen the case', + } +); +export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate( + 'xpack.cases.caseView.errorsPushServiceCallOutTitle', + { + defaultMessage: 'Select an external connector', + } +); diff --git a/x-pack/plugins/cases/public/components/callout/types.ts b/x-pack/plugins/cases/public/components/use_push_to_service/callout/types.ts similarity index 78% rename from x-pack/plugins/cases/public/components/callout/types.ts rename to x-pack/plugins/cases/public/components/use_push_to_service/callout/types.ts index 84d79ee391b8f..04d75fa8e4b06 100644 --- a/x-pack/plugins/cases/public/components/callout/types.ts +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/types.ts @@ -6,8 +6,10 @@ */ export interface ErrorMessage { + description: JSX.Element | string; + errorType?: 'primary' | 'success' | 'warning' | 'danger'; id: string; title: string; - description: JSX.Element; - errorType?: 'primary' | 'success' | 'warning' | 'danger'; } + +export const CLOSED_CASE_PUSH_ERROR_ID = 'closed-case-push-error'; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx index 302e45f5e7e70..230fda0e35f81 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx @@ -11,7 +11,7 @@ import React from 'react'; import * as i18n from './translations'; import { ActionLicense } from '../../containers/types'; -import { ErrorMessage } from '../callout/types'; +import { ErrorMessage } from './callout/types'; export const getLicenseError = () => ({ id: 'license-error', diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index fa52c361c26ac..fd94408300d20 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -11,12 +11,12 @@ import { renderHook, act } from '@testing-library/react-hooks'; import '../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; import { TestProviders } from '../../common/mock'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses, ConnectorTypes } from '../../../common'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { basicPush, actionLicenses } from '../../containers/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { connectorsMock } from '../../containers/configure/mock'; -import { ConnectorTypes } from '../../../common/api/connectors'; +import { CLOSED_CASE_PUSH_ERROR_ID } from './callout/types'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -36,6 +36,7 @@ jest.mock('../../containers/configure/api'); describe('usePushToService', () => { const caseId = '12345'; const updateCase = jest.fn(); + const onEditClick = jest.fn(); const pushCaseToExternalService = jest.fn(); const mockPostPush = { isLoading: false, @@ -55,6 +56,7 @@ describe('usePushToService', () => { }; const defaultArgs = { + actionsErrors: [], connector: { id: mockConnector.id, name: mockConnector.name, @@ -69,6 +71,8 @@ describe('usePushToService', () => { onClick: jest.fn(), }, connectors: connectorsMock, + hasDataToPush: true, + onEditClick, isValidConnector: true, updateCase, userCanCrud: true, @@ -265,7 +269,7 @@ describe('usePushToService', () => { await waitForNextUpdate(); const errorsMsg = result.current.pushCallouts?.props.messages; expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual('closed-case-push-error'); + expect(errorsMsg[0].id).toEqual(CLOSED_CASE_PUSH_ERROR_ID); }); }); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx index 6f711150b7744..976d85b952e2b 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx @@ -5,31 +5,32 @@ * 2.0. */ -import { EuiButton, EuiToolTip } from '@elastic/eui'; +import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; -import { Case } from '../../containers/types'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { CaseCallOut } from '../callout'; +import { CaseCallOut } from './callout'; import { getLicenseError, getKibanaConfigError } from './helpers'; import * as i18n from './translations'; -import { CaseConnector, ActionConnector, CaseStatuses } from '../../../common'; +import { Case, CaseConnector, ActionConnector, CaseStatuses } from '../../../common'; import { CaseServices } from '../../containers/use_get_case_user_actions'; -import { CasesNavigation, LinkAnchor } from '../links'; -import { ErrorMessage } from '../callout/types'; +import { CasesNavigation } from '../links'; +import { CLOSED_CASE_PUSH_ERROR_ID, ErrorMessage } from './callout/types'; export interface UsePushToService { caseId: string; + caseServices: CaseServices; caseStatus: string; configureCasesNavigation: CasesNavigation; connector: CaseConnector; - caseServices: CaseServices; connectors: ActionConnector[]; + hasDataToPush: boolean; + isValidConnector: boolean; + onEditClick: () => void; updateCase: (newCase: Case) => void; userCanCrud: boolean; - isValidConnector: boolean; } export interface ReturnUsePushToService { @@ -38,15 +39,17 @@ export interface ReturnUsePushToService { } export const usePushToService = ({ - configureCasesNavigation: { href }, - connector, caseId, caseServices, caseStatus, + configureCasesNavigation, + connector, connectors, + hasDataToPush, + isValidConnector, + onEditClick, updateCase, userCanCrud, - isValidConnector, }: UsePushToService): ReturnUsePushToService => { const { isLoading, pushCaseToExternalService } = usePostPushToService(); @@ -83,20 +86,8 @@ export const usePushToService = ({ ...errors, { id: 'connector-missing-error', - title: i18n.PUSH_DISABLE_BY_NO_CONFIG_TITLE, - description: ( - - {i18n.LINK_CONNECTOR_CONFIGURE} - - ), - }} - /> - ), + title: '', + description: i18n.CONFIGURE_CONNECTOR, }, ]; } else if (connector.id === 'none' && !loadingLicense) { @@ -104,13 +95,8 @@ export const usePushToService = ({ ...errors, { id: 'connector-not-selected-error', - title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE, - description: ( - - ), + title: '', + description: i18n.CONFIGURE_CONNECTOR, }, ]; } else if (!isValidConnector && !loadingLicense) { @@ -118,7 +104,7 @@ export const usePushToService = ({ ...errors, { id: 'connector-deleted-error', - title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE, + title: '', description: ( ( - 0 || !userCanCrud || !isValidConnector + isLoading || + loadingLicense || + errorsMsg.length > 0 || + !userCanCrud || + !isValidConnector || + !hasDataToPush } isLoading={isLoading} > {caseServices[connector.id] ? i18n.UPDATE_THIRD(connector.name) : i18n.PUSH_THIRD(connector.name)} - + ), // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -175,6 +165,7 @@ export const usePushToService = ({ connectors, errorsMsg, handlePushToService, + hasDataToPush, isLoading, loadingLicense, userCanCrud, @@ -185,11 +176,15 @@ export const usePushToService = ({ const objToReturn = useMemo( () => ({ pushButton: - errorsMsg.length > 0 ? ( + errorsMsg.length > 0 || !hasDataToPush ? ( {errorsMsg[0].description}

} + title={ + errorsMsg.length > 0 ? errorsMsg[0].title : i18n.PUSH_LOCKED_TITLE(connector.name) + } + content={ +

{errorsMsg.length > 0 ? errorsMsg[0].description : i18n.PUSH_LOCKED_DESC}

+ } > {pushToServiceButton}
@@ -198,10 +193,23 @@ export const usePushToService = ({ ), pushCallouts: errorsMsg.length > 0 ? ( - + 0} + messages={errorsMsg} + onEditClick={onEditClick} + /> ) : null, }), - [errorsMsg, pushToServiceButton] + [ + configureCasesNavigation, + connector.name, + connectors.length, + errorsMsg, + hasDataToPush, + onEditClick, + pushToServiceButton, + ] ); return objToReturn; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts index fd6faa634e053..353aa9b92fafc 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts +++ b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts @@ -7,12 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate( - 'xpack.cases.caseView.errorsPushServiceCallOutTitle', - { - defaultMessage: 'To send cases to external systems, you need to:', - } -); export const PUSH_THIRD = (thirdParty: string) => { if (thirdParty === 'none') { return i18n.translate('xpack.cases.caseView.pushThirdPartyIncident', { @@ -39,24 +33,28 @@ export const UPDATE_THIRD = (thirdParty: string) => { }); }; -export const PUSH_DISABLE_BY_NO_CONFIG_TITLE = i18n.translate( - 'xpack.cases.caseView.pushToServiceDisableByNoConfigTitle', - { - defaultMessage: 'Configure external connector', +export const PUSH_LOCKED_TITLE = (thirdParty: string) => { + if (thirdParty === 'none') { + return i18n.translate('xpack.cases.caseView.lockedIncidentTitleNone', { + defaultMessage: 'External incident is up to date', + }); } -); -export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate( - 'xpack.cases.caseView.pushToServiceDisableByNoCaseConfigTitle', - { - defaultMessage: 'Select external connector', - } -); + return i18n.translate('xpack.cases.caseView.lockedIncidentTitle', { + values: { thirdParty }, + defaultMessage: '{ thirdParty } incident is up to date', + }); +}; + +export const PUSH_LOCKED_DESC = i18n.translate('xpack.cases.caseView.lockedIncidentDesc', { + defaultMessage: 'No update is required', +}); -export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( - 'xpack.cases.caseView.pushToServiceDisableBecauseCaseClosedTitle', +export const CONFIGURE_CONNECTOR = i18n.translate( + 'xpack.cases.caseView.pushToService.configureConnector', { - defaultMessage: 'Reopen the case', + defaultMessage: + 'To open and update cases in external systems, you must select an external incident management system for this case.', } ); @@ -81,10 +79,3 @@ export const LINK_CLOUD_DEPLOYMENT = i18n.translate('xpack.cases.caseView.cloudD export const LINK_APPROPRIATE_LICENSE = i18n.translate('xpack.cases.caseView.appropiateLicense', { defaultMessage: 'appropriate license', }); - -export const LINK_CONNECTOR_CONFIGURE = i18n.translate( - 'xpack.cases.caseView.connectorConfigureLink', - { - defaultMessage: 'connector', - } -); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx index c58f5e53c9fd7..faa4f1d1a786f 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx @@ -36,6 +36,7 @@ const defaultProps = { isLoadingUserActions: false, onUpdateField, selectedAlertPatterns: ['some-test-pattern'], + statusActionButton: null, updateCase, userCanCrud: true, useFetchAlertData: (): [boolean, Record] => [ diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index c7cc71da92947..5e90c2e6a951f 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -67,6 +67,7 @@ export interface UserActionTreeProps { onShowAlertDetails: (alertId: string, index: string) => void; onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; renderInvestigateInTimelineActionComponent?: (alertIds: string[]) => JSX.Element; + statusActionButton: JSX.Element | null; updateCase: (newCase: Case) => void; useFetchAlertData: (alertIds: string[]) => [boolean, Record]; userCanCrud: boolean; @@ -130,6 +131,7 @@ export const UserActionTree = React.memo( onShowAlertDetails, onUpdateField, renderInvestigateInTimelineActionComponent, + statusActionButton, updateCase, useFetchAlertData, userCanCrud, @@ -246,10 +248,11 @@ export const UserActionTree = React.memo( onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_ID)} showLoading={false} + statusActionButton={statusActionButton} subCaseId={subCaseId} /> ), - [caseId, userCanCrud, handleUpdate, handleManageMarkdownEditId, subCaseId] + [caseId, userCanCrud, handleUpdate, handleManageMarkdownEditId, statusActionButton, subCaseId] ); useEffect(() => { diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 72fee3c602c4e..df03311005bdb 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -86,7 +86,7 @@ export const basicCase: Case = { createdAt: basicCreatedAt, createdBy: elasticUser, connector: { - id: '123', + id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null, @@ -117,7 +117,7 @@ export const collectionCase: Case = { createdAt: basicCreatedAt, createdBy: elasticUser, connector: { - id: '123', + id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null, @@ -251,7 +251,7 @@ export const basicCaseSnake: CaseResponse = { closed_by: null, comments: [basicCommentSnake], connector: { - id: '123', + id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null, diff --git a/x-pack/plugins/cases/public/containers/use_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx index 666e8df0c2413..8d7cfc56b195c 100644 --- a/x-pack/plugins/cases/public/containers/use_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx @@ -85,7 +85,7 @@ describe('useUpdateCase', () => { isError: false, updateCaseProperty: result.current.updateCaseProperty, }); - expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, '123', undefined); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, 'none', undefined); expect(updateCase).toBeCalledWith(basicCase); expect(onSuccess).toHaveBeenCalled(); }); @@ -105,7 +105,7 @@ describe('useUpdateCase', () => { isError: false, updateCaseProperty: result.current.updateCaseProperty, }); - expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, '123', basicSubCaseId); + expect(fetchCaseUserActions).toBeCalledWith(basicCase.id, 'none', basicSubCaseId); expect(updateCase).toBeCalledWith(basicCase); expect(onSuccess).toHaveBeenCalled(); }); From 82e32faf1a1ee3818ed028946e67f5d34d44e509 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 28 Jun 2021 21:44:11 +0200 Subject: [PATCH 18/74] Locator docs (#103129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add locator_examples plugin * feat: 🎸 add example app in locator_examples * feat: 🎸 add locator_explorer plugin * chore: 🤖 remove url_generaotrs_* example plugins * docs: ✏️ update share plugin readme * docs: ✏️ add locators readme * docs: ✏️ update docs link in example plugin * docs: ✏️ update navigation docs * fix: 🐛 make P extend SerializableState * test: 💍 update test mocks * fix: 🐛 use correct type in ingest pipeline locator * test: 💍 add missing methods in mock * test: 💍 update test mocks * chore: 🤖 update plugin list Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../best-practices/navigation.asciidoc | 18 +- docs/developer/plugin-list.asciidoc | 3 +- examples/locator_examples/README.md | 8 + .../kibana.json | 4 +- .../public/app.tsx | 0 .../public/index.ts | 12 +- examples/locator_examples/public/locator.ts | 54 ++++++ .../public/plugin.tsx | 41 ++--- .../tsconfig.json | 0 .../README.md | 5 +- .../kibana.json | 4 +- .../public/app.tsx | 77 ++++---- .../public/index.ts | 4 +- .../public/page.tsx | 0 .../public/plugin.tsx | 32 ++-- .../tsconfig.json | 0 examples/url_generators_examples/README.md | 7 - .../public/url_generator.ts | 68 ------- src/plugins/discover/public/mocks.ts | 8 + src/plugins/management/public/mocks/index.ts | 4 + src/plugins/share/README.md | 33 ++-- .../common/url_service/locators/README.md | 166 ++++++++++++++++++ .../url_service/locators/locator_client.ts | 2 +- .../common/url_service/locators/types.ts | 4 +- .../explore_data_chart_action.test.ts | 4 + .../explore_data_context_menu_action.test.ts | 4 + .../ingest_pipelines/public/locator.test.ts | 4 + .../ingest_pipelines/public/locator.ts | 2 +- 28 files changed, 380 insertions(+), 188 deletions(-) create mode 100644 examples/locator_examples/README.md rename examples/{url_generators_examples => locator_examples}/kibana.json (74%) rename examples/{url_generators_examples => locator_examples}/public/app.tsx (100%) rename examples/{url_generators_explorer => locator_examples}/public/index.ts (59%) create mode 100644 examples/locator_examples/public/locator.ts rename examples/{url_generators_examples => locator_examples}/public/plugin.tsx (53%) rename examples/{url_generators_examples => locator_examples}/tsconfig.json (100%) rename examples/{url_generators_explorer => locator_explorer}/README.md (79%) rename examples/{url_generators_explorer => locator_explorer}/kibana.json (50%) rename examples/{url_generators_explorer => locator_explorer}/public/app.tsx (68%) rename examples/{url_generators_examples => locator_explorer}/public/index.ts (75%) rename examples/{url_generators_explorer => locator_explorer}/public/page.tsx (100%) rename examples/{url_generators_explorer => locator_explorer}/public/plugin.tsx (63%) rename examples/{url_generators_explorer => locator_explorer}/tsconfig.json (100%) delete mode 100644 examples/url_generators_examples/README.md delete mode 100644 examples/url_generators_examples/public/url_generator.ts create mode 100644 src/plugins/share/common/url_service/locators/README.md diff --git a/docs/developer/best-practices/navigation.asciidoc b/docs/developer/best-practices/navigation.asciidoc index d01f2c2aa0f95..32946a2f74bd9 100644 --- a/docs/developer/best-practices/navigation.asciidoc +++ b/docs/developer/best-practices/navigation.asciidoc @@ -47,24 +47,26 @@ console.log(discoverUrl); // http://localhost:5601/bpr/s/space/app/discover const discoverUrlWithSomeState = core.http.basePath.prepend(`/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:'2020-09-10T11:39:50.203Z',to:'2020-09-10T11:40:20.249Z'))&_a=(columns:!(_source),filters:!(),index:'90943e30-9a47-11e8-b64d-95841ca0b247',interval:auto,query:(language:kuery,query:''),sort:!())`); ---- -Instead, each app should expose {kib-repo}tree/{branch}/src/plugins/share/public/url_generators/README.md[a URL generator]. -Other apps should use those URL generators for creating URLs. +Instead, each app should expose {kib-repo}tree/{branch}/src/plugins/share/common/url_service/locators/README.md[a locator]. +Other apps should use those locators for navigation or URL creation. [source,typescript jsx] ---- -// Properly generated URL to *Discover* app. Generator code is owned by *Discover* app and available on *Discover*'s plugin contract. -const discoverUrl = discoverUrlGenerator.createUrl({filters, timeRange}); +// Properly generated URL to *Discover* app. Locator code is owned by *Discover* app and available on *Discover*'s plugin contract. +const discoverUrl = await plugins.discover.locator.getUrl({filters, timeRange}); +// or directly execute navigation +await plugins.discover.locator.navigate({filters, timeRange}); ---- -To get a better idea, take a look at *Discover* URL generator {kib-repo}tree/{branch}/src/plugins/discover/public/url_generator.ts[implementation]. +To get a better idea, take a look at *Discover* locator {kib-repo}tree/{branch}/src/plugins/discover/public/locator.ts[implementation]. It allows specifying various **Discover** app state pieces like: index pattern, filters, query, time range and more. -There are two ways to access other's app URL generator in your code: +There are two ways to access locators of other apps: 1. From a plugin contract of a destination app *(preferred)*. -2. Using URL generator service instance on `share` plugin contract (in case an explicit plugin dependency is not possible). +2. Using locator client in `share` plugin (case an explicit plugin dependency is not possible). -In case you want other apps to link to your app, then you should create a URL generator and expose it on your plugin's contract. +In case you want other apps to link to your app, then you should create a locator and expose it on your plugin's contract. [[navigating-between-kibana-apps]] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 96326b739422f..231e089950a28 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -191,7 +191,8 @@ so they can properly protect the data within their clusters. |{kib-repo}blob/{branch}/src/plugins/share/README.md[share] -|Replaces the legacy ui/share module for registering share context menus. +|The share plugin contains various utilities for displaying sharing context menu, +generating deep links to other apps, and creating short URLs. |{kib-repo}blob/{branch}/src/plugins/spaces_oss/README.md[spacesOss] diff --git a/examples/locator_examples/README.md b/examples/locator_examples/README.md new file mode 100644 index 0000000000000..bcc1b19f689d1 --- /dev/null +++ b/examples/locator_examples/README.md @@ -0,0 +1,8 @@ +# Locator examples + +This example plugin shows how to: + + - Register a URL locator. + - Return locator from plugin contract. + +To run this example, use the command `yarn start --run-examples`. Navigate to the locator app. diff --git a/examples/url_generators_examples/kibana.json b/examples/locator_examples/kibana.json similarity index 74% rename from examples/url_generators_examples/kibana.json rename to examples/locator_examples/kibana.json index 9658f5c7300aa..df336b2ab3613 100644 --- a/examples/url_generators_examples/kibana.json +++ b/examples/locator_examples/kibana.json @@ -1,5 +1,5 @@ { - "id": "urlGeneratorsExamples", + "id": "locatorExamples", "version": "0.0.1", "kibanaVersion": "kibana", "server": false, @@ -7,6 +7,6 @@ "requiredPlugins": ["share"], "optionalPlugins": [], "extraPublicDirs": [ - "public/url_generator" + "public/locator" ] } diff --git a/examples/url_generators_examples/public/app.tsx b/examples/locator_examples/public/app.tsx similarity index 100% rename from examples/url_generators_examples/public/app.tsx rename to examples/locator_examples/public/app.tsx diff --git a/examples/url_generators_explorer/public/index.ts b/examples/locator_examples/public/index.ts similarity index 59% rename from examples/url_generators_explorer/public/index.ts rename to examples/locator_examples/public/index.ts index 8a78f3214453d..50da3501805fa 100644 --- a/examples/url_generators_explorer/public/index.ts +++ b/examples/locator_examples/public/index.ts @@ -6,6 +6,14 @@ * Side Public License, v 1. */ -import { AccessLinksExplorerPlugin } from './plugin'; +import { LocatorExamplesPlugin } from './plugin'; -export const plugin = () => new AccessLinksExplorerPlugin(); +export { + HelloLocator, + HelloLocatorV1Params, + HelloLocatorV2Params, + HelloLocatorParams, + HELLO_LOCATOR, +} from './locator'; + +export const plugin = () => new LocatorExamplesPlugin(); diff --git a/examples/locator_examples/public/locator.ts b/examples/locator_examples/public/locator.ts new file mode 100644 index 0000000000000..18caeca08564e --- /dev/null +++ b/examples/locator_examples/public/locator.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SerializableState, MigrateFunction } from 'src/plugins/kibana_utils/common'; +import { LocatorDefinition, LocatorPublic } from '../../../src/plugins/share/public'; + +export const HELLO_LOCATOR = 'HELLO_LOCATOR'; + +export interface HelloLocatorV1Params extends SerializableState { + name: string; +} + +export interface HelloLocatorV2Params extends SerializableState { + firstName: string; + lastName: string; +} + +export type HelloLocatorParams = HelloLocatorV2Params; + +const migrateV1ToV2: MigrateFunction = ( + v1: HelloLocatorV1Params +) => { + const v2: HelloLocatorV2Params = { + firstName: v1.name, + lastName: '', + }; + + return v2; +}; + +export type HelloLocator = LocatorPublic; + +export class HelloLocatorDefinition implements LocatorDefinition { + public readonly id = HELLO_LOCATOR; + + public readonly getLocation = async ({ firstName, lastName }: HelloLocatorParams) => { + return { + app: 'locatorExamples', + path: `/hello?firstName=${encodeURIComponent(firstName)}&lastName=${encodeURIComponent( + lastName + )}`, + state: {}, + }; + }; + + public readonly migrations = { + '0.0.2': (migrateV1ToV2 as unknown) as MigrateFunction, + }; +} diff --git a/examples/url_generators_examples/public/plugin.tsx b/examples/locator_examples/public/plugin.tsx similarity index 53% rename from examples/url_generators_examples/public/plugin.tsx rename to examples/locator_examples/public/plugin.tsx index f797c92d4c902..4364c46e6138c 100644 --- a/examples/url_generators_examples/public/plugin.tsx +++ b/examples/locator_examples/public/plugin.tsx @@ -8,44 +8,27 @@ import { SharePluginStart, SharePluginSetup } from '../../../src/plugins/share/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; -import { - HelloLinkGeneratorState, - createHelloPageLinkGenerator, - LegacyHelloLinkGeneratorState, - HELLO_URL_GENERATOR_V1, - HELLO_URL_GENERATOR, - helloPageLinkGeneratorV1, -} from './url_generator'; +import { HelloLocator, HelloLocatorDefinition } from './locator'; -declare module '../../../src/plugins/share/public' { - export interface UrlGeneratorStateMapping { - [HELLO_URL_GENERATOR_V1]: LegacyHelloLinkGeneratorState; - [HELLO_URL_GENERATOR]: HelloLinkGeneratorState; - } +interface SetupDeps { + share: SharePluginSetup; } interface StartDeps { share: SharePluginStart; } -interface SetupDeps { - share: SharePluginSetup; +export interface LocatorExamplesSetup { + locator: HelloLocator; } -const APP_ID = 'urlGeneratorsExamples'; - -export class AccessLinksExamplesPlugin implements Plugin { - public setup(core: CoreSetup, { share: { urlGenerators } }: SetupDeps) { - urlGenerators.registerUrlGenerator( - createHelloPageLinkGenerator(async () => ({ - appBasePath: (await core.getStartServices())[0].application.getUrlForApp(APP_ID), - })) - ); - - urlGenerators.registerUrlGenerator(helloPageLinkGeneratorV1); +export class LocatorExamplesPlugin + implements Plugin { + public setup(core: CoreSetup, plugins: SetupDeps) { + const locator = plugins.share.url.locators.create(new HelloLocatorDefinition()); core.application.register({ - id: APP_ID, + id: 'locatorExamples', title: 'Access links examples', navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { @@ -58,6 +41,10 @@ export class AccessLinksExamplesPlugin implements Plugin { +const ActionsExplorer = ({ share }: Props) => { const [migratedLinks, setMigratedLinks] = useState([] as MigratedLink[]); const [buildingLinks, setBuildingLinks] = useState(false); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); + /** * Lets pretend we grabbed these links from a persistent store, like a saved object. - * Some of these links were created with older versions of the hello link generator. - * They use deprecated generator ids. + * Some of these links were created with older versions of the hello locator. */ - const [persistedLinks, setPersistedLinks] = useState([ + const [persistedLinks, setPersistedLinks] = useState< + Array<{ + id: string; + version: string; + linkText: string; + params: HelloLocatorV1Params | HelloLocatorV2Params; + }> + >([ { - id: HELLO_URL_GENERATOR_V1, + id: HELLO_LOCATOR, + version: '0.0.1', linkText: 'Say hello to Mary', - state: { + params: { name: 'Mary', }, }, { - id: HELLO_URL_GENERATOR, + id: HELLO_LOCATOR, + version: '0.0.2', linkText: 'Say hello to George', - state: { + params: { firstName: 'George', lastName: 'Washington', }, @@ -71,30 +79,38 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { const updateLinks = async () => { const updatedLinks = await Promise.all( persistedLinks.map(async (savedLink) => { - const generator = getLinkGenerator(savedLink.id); - const link = await generator.createUrl(savedLink.state); + const locator = share.url.locators.get(savedLink.id); + if (!locator) return; + let params: HelloLocatorV1Params | HelloLocatorV2Params = savedLink.params; + if (savedLink.version === '0.0.1') { + const migration = locator.migrations['0.0.2']; + if (migration) { + params = migration(params) as HelloLocatorV2Params; + } + } + const link = await locator.getUrl(params, { absolute: false }); return { - isDeprecated: generator.isDeprecated, linkText: savedLink.linkText, link, - }; + version: savedLink.version, + } as MigratedLink; }) ); - setMigratedLinks(updatedLinks); + setMigratedLinks(updatedLinks as MigratedLink[]); setBuildingLinks(false); }; updateLinks(); - }, [getLinkGenerator, persistedLinks]); + }, [share, persistedLinks]); return ( - Access links explorer + Locator explorer -

Create new links using the most recent version of a url generator.

+

Create new links using the most recent version of a locator.

{ setPersistedLinks([ ...persistedLinks, { - id: HELLO_URL_GENERATOR, - state: { firstName, lastName }, + id: HELLO_LOCATOR, + version: '0.0.2', + params: { firstName, lastName }, linkText: `Say hello to ${firstName} ${lastName}`, }, ]) @@ -122,10 +139,10 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => {

Existing links retrieved from storage. The links that were generated from legacy - generators are in red. This can be useful for developers to know they will have to + locators are in red. This can be useful for developers to know they will have to migrate persisted state or in a future version of Kibana, these links may no longer - work. They still work now because legacy url generators must provide a state - migration function. + work. They still work now because legacy locators must provide state migration + functions.

{buildingLinks ? ( @@ -134,7 +151,7 @@ const ActionsExplorer = ({ getLinkGenerator }: Props) => { migratedLinks.map((link) => ( new AccessLinksExamplesPlugin(); +export const plugin = () => new LocatorExplorerPlugin(); diff --git a/examples/url_generators_explorer/public/page.tsx b/examples/locator_explorer/public/page.tsx similarity index 100% rename from examples/url_generators_explorer/public/page.tsx rename to examples/locator_explorer/public/page.tsx diff --git a/examples/url_generators_explorer/public/plugin.tsx b/examples/locator_explorer/public/plugin.tsx similarity index 63% rename from examples/url_generators_explorer/public/plugin.tsx rename to examples/locator_explorer/public/plugin.tsx index f5f12df669d6c..3e8382f2a606f 100644 --- a/examples/url_generators_explorer/public/plugin.tsx +++ b/examples/locator_explorer/public/plugin.tsx @@ -6,30 +6,30 @@ * Side Public License, v 1. */ -import { SharePluginStart } from '../../../src/plugins/share/public'; +import { SharePluginSetup, SharePluginStart } from '../../../src/plugins/share/public'; import { Plugin, CoreSetup, AppMountParameters, AppNavLinkStatus } from '../../../src/core/public'; import { DeveloperExamplesSetup } from '../../developer_examples/public'; -interface StartDeps { - share: SharePluginStart; -} - interface SetupDeps { developerExamples: DeveloperExamplesSetup; + share: SharePluginSetup; +} + +interface StartDeps { + share: SharePluginStart; } -export class AccessLinksExplorerPlugin implements Plugin { - public setup(core: CoreSetup, { developerExamples }: SetupDeps) { +export class LocatorExplorerPlugin implements Plugin { + public setup(core: CoreSetup, { developerExamples, share }: SetupDeps) { core.application.register({ - id: 'urlGeneratorsExplorer', - title: 'Access links explorer', + id: 'locatorExplorer', + title: 'Locator explorer', navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - const depsStart = (await core.getStartServices())[1]; const { renderApp } = await import('./app'); return renderApp( { - getLinkGenerator: depsStart.share.urlGenerators.getUrlGenerator, + share, }, params ); @@ -37,18 +37,18 @@ export class AccessLinksExplorerPlugin implements Plugin; - -export const createHelloPageLinkGenerator = ( - getStartServices: () => Promise<{ appBasePath: string }> -): UrlGeneratorsDefinition => ({ - id: HELLO_URL_GENERATOR, - createUrl: async (state) => { - const startServices = await getStartServices(); - const appBasePath = startServices.appBasePath; - const parsedUrl = url.parse(window.location.href); - - return url.format({ - protocol: parsedUrl.protocol, - host: parsedUrl.host, - pathname: `${appBasePath}/hello`, - query: { - ...state, - }, - }); - }, -}); - -/** - * The name of this legacy generator id changes, but the *value* stays the same. - */ -export const HELLO_URL_GENERATOR_V1 = 'HELLO_URL_GENERATOR'; - -export interface HelloLinkStateV1 { - name: string; -} - -export type LegacyHelloLinkGeneratorState = UrlGeneratorState< - HelloLinkStateV1, - typeof HELLO_URL_GENERATOR, - HelloLinkState ->; - -export const helloPageLinkGeneratorV1: UrlGeneratorsDefinition = { - id: HELLO_URL_GENERATOR_V1, - isDeprecated: true, - migrate: async (state) => { - return { id: HELLO_URL_GENERATOR, state: { firstName: state.name, lastName: '' } }; - }, -}; diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index 53160df472a3c..e2000e422f227 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -21,6 +21,10 @@ const createSetupContract = (): Setup => { getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }; return setupContract; @@ -37,6 +41,10 @@ const createStartContract = (): Start => { getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }; return startContract; diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index b06e41502e9df..ce7bc94c8f9cf 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -39,6 +39,10 @@ const createSetupContract = (): ManagementSetup => ({ getUrl: jest.fn(), useUrl: jest.fn(), navigate: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }, }); diff --git a/src/plugins/share/README.md b/src/plugins/share/README.md index 7ecf23134cf24..b3ce5e8832244 100644 --- a/src/plugins/share/README.md +++ b/src/plugins/share/README.md @@ -1,24 +1,23 @@ # Share plugin -Replaces the legacy `ui/share` module for registering share context menus. +The `share` plugin contains various utilities for displaying sharing context menu, +generating deep links to other apps, and creating short URLs. -## Example registration -```ts -// For legacy plugins -import { npSetup } from 'ui/new_platform'; -npSetup.plugins.share.register(/* same details here */); +## Sharing context menu -// For new plugins: first add 'share' to the list of `optionalPlugins` -// in your kibana.json file. Then access the plugin directly in `setup`: +You can register an item into sharing context menu (which is displayed in +Dahsboard, Discover, and Visuzlize apps). -class MyPlugin { - setup(core, plugins) { - if (plugins.share) { - plugins.share.register(/* same details here. */); - } - } -} -``` +### Example registration -Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. +```ts +import { ShareContext, ShareMenuItem } from 'src/plugins/share/public'; + +plugins.share.register({ + id: 'MY_MENU', + getShareMenuItems: (context: ShareContext): ShareMenuItem[] => { + // ... + }, +}; +``` diff --git a/src/plugins/share/common/url_service/locators/README.md b/src/plugins/share/common/url_service/locators/README.md new file mode 100644 index 0000000000000..1ca990f92d7b8 --- /dev/null +++ b/src/plugins/share/common/url_service/locators/README.md @@ -0,0 +1,166 @@ +# Locators + +## Locators service + +Developers who maintain pages in Kibana that other developers may want to link to +can register a *locator*. Locators provide backward compatibility support +so the developer of the app/page has a way to change their url structure without +breaking users of this system. If users were to generate the URLs on their own, +using string concatenation, those links may break often. + +Owners: __Kibana App Services team__. + + +## Producer Usage + +Here is how you create a *locator*, which deeply link into your Kibana app: + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { productId: string }) => { + return { + app: 'myApp', + path: `/products/${productId}`, + state: {}, + }; + }, +}); +``` + +When navigation in Kibana is done without a page reload a serializable *location state* +object is passed to the destination app, which the app can use to change its +appearance. The *location state* object does not appear in the URL, but apps +can still use that, similar to how URL parameters are used. + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { productId: string, tab?: 'pics' | 'attributes' }) => { + return { + app: 'myApp', + path: `/products/${productId}`, + state: { + tab: params.tab || 'pics', + }, + }; + }, +}); +``` + +When you want to change the shape of the parameters that the locator receives, you can +provide a migration function, which can transform the shape of the parameters from +one Kibana version to another. For example, below we replace `productId` param by `id`. + +```ts +plugins.share.url.locators.create({ + id: 'MY_APP_LOCATOR', + getLocation: async (params: { id: string, tab?: 'pics' | 'attributes' }) => { + return { + app: 'myApp', + path: `/products/${id}`, + state: { + tab: params.tab || 'pics', + }, + }; + }, + + migrations = { + '7.20.0': ({productId, ...rest}) => { + return { + id: productId, + ...rest, + }; + }, + }, +}); +``` + +The migration version should correspond to Kibana relase when the chagne was +introduced. It is the responsibility of the *consumer* to make sure they +migrate their stored parameters using the provided migration function to the +latest version. Migrations versions are ordered by semver. As a consumer, +if persist somewhere a locator parameters object, you also want to store +the version of that object, so later you know from starting from which +version you need to execute migrations. + + +## Consumer Usage + +Consumers of the Locators service can use the locators to generate deeps links +into Kibana apps, or navigate to the apps while passing to the destination +app the *location state*. + +First you will need to get hold of the *locator* for the app you want to +navigate to. + +Usually, that app will return it from its plugin contract from the "setup" +life-cycle: + +```ts +class MyPlugin { + setup(core, plugins) { + const locator = plugins.destinationApp.locator; + } +} +``` + +Or, you can get hold of any locator from the central registry: + +```ts +class MyPlugin { + setup(core, plugins) { + const locator = plugins.share.url.locators.get('DESTINATION_APP_LOCATOR'); + } +} +``` + +Once you have the locator, you can use it to navigate to some kibana app: + +```ts +await locator.navigate({ + productId: '123', +}); +``` + +You can also use it to generate a URL string of the destination: + +```ts +const url = await locator.getUrl({ + productId: '123', +}); +``` + +#### Migrations + +**As a consumer, you should not persist the resulting URL string!** + +As soon as you do, you have lost your migration options. Instead you should +store the ID, version and params of your locator. This will let you +re-create the migrated URL later. + +If, as a consumer, you store the ID, version and params of the locator, you +should use the migration functions provided by the locator when migrating +between Kibana versions. + +```ts +const migration = locator.migrations[version]; + +if (migration) { + params = migration(params); +} +``` + + +### Examples + +You can view the provided example plugins to learn more how to work with locators. +There are two plugins (`locator_examples` and `locator_explorer`) provided in the +`/examples` folder. You can run the example plugins using the following command: + +``` +yarn start --run-examples +``` + +To view the `locator_explorer` example plugin in Kibana navigate to: __Analytics__ 👉 +__Developer examples__ 👉 __URL locators__. diff --git a/src/plugins/share/common/url_service/locators/locator_client.ts b/src/plugins/share/common/url_service/locators/locator_client.ts index 168cc02d03ff1..fc6b23f94a386 100644 --- a/src/plugins/share/common/url_service/locators/locator_client.ts +++ b/src/plugins/share/common/url_service/locators/locator_client.ts @@ -41,7 +41,7 @@ export class LocatorClient implements ILocatorClient { * @param id ID of a URL locator. * @returns A public interface of a registered URL locator. */ - public get

(id: string): undefined | LocatorPublic

{ + public get

(id: string): undefined | LocatorPublic

{ return this.locators.get(id); } } diff --git a/src/plugins/share/common/url_service/locators/types.ts b/src/plugins/share/common/url_service/locators/types.ts index 870eaa3718d3f..0429d52a8f52d 100644 --- a/src/plugins/share/common/url_service/locators/types.ts +++ b/src/plugins/share/common/url_service/locators/types.ts @@ -25,7 +25,7 @@ export interface ILocatorClient { * * @param id Unique ID of the locator. */ - get

(id: string): undefined | LocatorPublic

; + get

(id: string): undefined | LocatorPublic

; } /** @@ -50,7 +50,7 @@ export interface LocatorDefinition

/** * Public interface of a registered locator. */ -export interface LocatorPublic

{ +export interface LocatorPublic

extends PersistableState

{ /** * Returns a reference to a Kibana client-side location. * diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts index 23ac882e4ecf7..84c81246781bb 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.test.ts @@ -55,6 +55,10 @@ const setup = ( navigate: jest.fn(async () => {}), getUrl: jest.fn(), useUrl: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }; const plugins: PluginDeps = { diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts index 5bdac602ec271..e0a8cf20ee943 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.test.ts @@ -41,6 +41,10 @@ const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } = navigate: jest.fn(async () => {}), getUrl: jest.fn(), useUrl: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + telemetry: jest.fn(), + migrations: {}, }; const plugins: PluginDeps = { diff --git a/x-pack/plugins/ingest_pipelines/public/locator.test.ts b/x-pack/plugins/ingest_pipelines/public/locator.test.ts index 0b1246b2bed59..47c7b13eb07ea 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.test.ts @@ -21,6 +21,10 @@ describe('Ingest pipeline locator', () => { throw new Error('not implemented'); }, useUrl: () => '', + telemetry: jest.fn(), + extract: jest.fn(), + inject: jest.fn(), + migrations: {}, }, }); return { definition }; diff --git a/x-pack/plugins/ingest_pipelines/public/locator.ts b/x-pack/plugins/ingest_pipelines/public/locator.ts index d819011f14f47..bfcc2f0fc9ce2 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.ts @@ -52,7 +52,7 @@ export type IngestPipelinesParams = | IngestPipelinesCloneParams | IngestPipelinesCreateParams; -export type IngestPipelinesLocator = LocatorPublic; +export type IngestPipelinesLocator = LocatorPublic; export const INGEST_PIPELINES_APP_LOCATOR = 'INGEST_PIPELINES_APP_LOCATOR'; From 906b4fcc29c36523c2de2c37d1e7935996d40d37 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 28 Jun 2021 20:48:32 +0100 Subject: [PATCH 19/74] skip flaky suite (#103538) --- x-pack/test/accessibility/apps/ml.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/ml.ts b/x-pack/test/accessibility/apps/ml.ts index 4babe0bd6ff88..382086728da01 100644 --- a/x-pack/test/accessibility/apps/ml.ts +++ b/x-pack/test/accessibility/apps/ml.ts @@ -59,7 +59,8 @@ export default function ({ getService }: FtrProviderContext) { }); }); - describe('with data loaded', function () { + // FLAKY: https://github.com/elastic/kibana/issues/103538 + describe.skip('with data loaded', function () { const adJobId = 'fq_single_a11y'; const dfaOutlierJobId = 'iph_outlier_a11y'; const calendarId = 'calendar_a11y'; From 4e20645feed7e5b1f1a5b604e14ffc2a2afb01f6 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Mon, 28 Jun 2021 15:53:19 -0400 Subject: [PATCH 20/74] Allow for versionless integration details urls (#103484) Default to either the installed version of an integration, or the latest available version based on installation status when a version is not included in the integration details URL. Closes #93393 --- .../fleet/server/services/epm/packages/get.ts | 10 +++++++++- .../fleet/server/services/epm/registry/index.test.ts | 12 ++++++------ .../fleet/server/services/epm/registry/index.ts | 10 ++++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 6a5968441e634..e493095bc4b36 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -114,9 +114,17 @@ export async function getPackageInfo(options: { Registry.fetchFindLatestPackage(pkgName), ]); + // If no package version is provided, use the installed version in the response + let responsePkgVersion = pkgVersion || savedObject?.attributes.install_version; + + // If no installed version of the given package exists, default to the latest version of the package + if (!responsePkgVersion) { + responsePkgVersion = latestPackage.version; + } + const getPackageRes = await getPackageFromSource({ pkgName, - pkgVersion, + pkgVersion: responsePkgVersion, savedObjectsClient, installedPkg: savedObject?.attributes, }); diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts index 16b57e1a9d011..1eeea399ff8a4 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.test.ts @@ -53,12 +53,6 @@ test('testPathParts', () => { }); describe('splitPkgKey tests', () => { - it('throws an error if the delimiter is not found', () => { - expect(() => { - splitPkgKey('awesome_package'); - }).toThrow(); - }); - it('throws an error if there is nothing before the delimiter', () => { expect(() => { splitPkgKey('-0.0.1-dev1'); @@ -71,6 +65,12 @@ describe('splitPkgKey tests', () => { }).toThrow(); }); + it('returns name and empty version if no delimiter is found', () => { + const { pkgName, pkgVersion } = splitPkgKey('awesome_package'); + expect(pkgName).toBe('awesome_package'); + expect(pkgVersion).toBe(''); + }); + it('returns the name and version if the delimiter is found once', () => { const { pkgName, pkgVersion } = splitPkgKey('awesome-0.1.0'); expect(pkgName).toBe('awesome'); diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 011a0e74e8c18..2dff3f43f3ec1 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -52,8 +52,14 @@ export interface SearchParams { * @param pkgkey a string containing the package name delimited by the package version */ export function splitPkgKey(pkgkey: string): { pkgName: string; pkgVersion: string } { - // this will return an empty string if `indexOf` returns -1 - const pkgName = pkgkey.substr(0, pkgkey.indexOf('-')); + // If no version is provided, use the provided package key as the + // package name and return an empty version value + if (!pkgkey.includes('-')) { + return { pkgName: pkgkey, pkgVersion: '' }; + } + + const pkgName = pkgkey.includes('-') ? pkgkey.substr(0, pkgkey.indexOf('-')) : pkgkey; + if (pkgName === '') { throw new PackageKeyInvalidError('Package key parsing failed: package name was empty'); } From f3a5ff2b98b9f4507445736c15ea6630849f0e82 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 28 Jun 2021 20:56:05 +0100 Subject: [PATCH 21/74] skip flaky suite (#100968) --- x-pack/test/accessibility/apps/spaces.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts index f2e79336e02ba..afd9295d9e6b8 100644 --- a/x-pack/test/accessibility/apps/spaces.ts +++ b/x-pack/test/accessibility/apps/spaces.ts @@ -114,7 +114,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // test starts with deleting space b so we can get the space selection page instead of logging out in the test - it('a11y test for space selection page', async () => { + // FLAKY: https://github.com/elastic/kibana/issues/100968 + it.skip('a11y test for space selection page', async () => { await PageObjects.spaceSelector.confirmDeletingSpace(); await a11y.testAppSnapshot(); await PageObjects.spaceSelector.clickSpaceCard('default'); From d655c05ef0bde2f47e0568555c6edc354b8d06e0 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 28 Jun 2021 14:12:55 -0700 Subject: [PATCH 22/74] [Enterprise Search] Add beta notification (#103535) * Set up new BetaNotification component * Update shared page template to append new beta notification item to side nav NOTE: I'm mutating the array because: - returning a new instance leads to a lot of really annoying type errors - the side nav's we're getting are entirely static with predictable items & and always come from us anyway - this is eventually going to get removed, and I'm optimizing for easy-to-remove code * Add beta notification to error connecting state - to help users/SDH cases where users cannot connect at all * Fix type error - sideNav itself can be undefined but not `sideNav.items` --- .../shared/error_state/error_state_prompt.tsx | 10 +- .../applications/shared/layout/beta.scss | 27 ++++ .../applications/shared/layout/beta.test.tsx | 131 ++++++++++++++++++ .../applications/shared/layout/beta.tsx | 109 +++++++++++++++ .../shared/layout/page_template.test.tsx | 2 + .../shared/layout/page_template.tsx | 4 + 6 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index f855c7b67dc6e..5636b56cc33af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -12,7 +12,8 @@ import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KibanaLogic } from '../../shared/kibana'; +import { KibanaLogic } from '../kibana'; +import { BetaNotification } from '../layout/beta'; import { EuiButtonTo } from '../react_router_helpers'; import './error_state_prompt.scss'; @@ -92,14 +93,15 @@ export const ErrorStatePrompt: React.FC = () => { } - actions={ + actions={[ - - } + , + , + ]} /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss new file mode 100644 index 0000000000000..6ba90cba381c4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.scss @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +@include euiBreakpoint('m', 'l', 'xl') { + .kbnPageTemplateSolutionNav { + position: relative; + min-height: 100%; + + // Nested to override EUI specificity + .betaNotificationSideNavItem { + margin-top: $euiSizeL; + } + } + + .betaNotificationWrapper { + position: absolute; + bottom: 3px; // Without this 3px buffer, the popover won't render to the right + } +} + +.betaNotification { + width: 350px; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx new file mode 100644 index 0000000000000..99b42b6f915e3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.test.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../__mocks__/enterprise_search_url.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiPopover, EuiPopoverTitle, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { shallowWithIntl } from '../../test_helpers'; + +import { BetaNotification, appendBetaNotificationItem } from './beta'; + +describe('BetaNotification', () => { + const getToggleButton = (wrapper: ShallowWrapper) => { + return shallow(

{wrapper.prop('button')}
).childAt(0); + }; + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.type()).toEqual(EuiPopover); + expect(wrapper.find(EuiPopoverTitle).prop('children')).toEqual( + 'Enterprise Search in Kibana is a beta user interface' + ); + }); + + describe('open/close popover state', () => { + const wrapper = shallow(); + + it('is initially closed', () => { + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); + }); + + it('opens the popover when the toggle button is pressed', () => { + getToggleButton(wrapper).simulate('click'); + + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(true); + }); + + it('closes the popover', () => { + wrapper.prop('closePopover')(); + + expect(wrapper.find(EuiPopover).prop('isOpen')).toBe(false); + }); + }); + + describe('toggle button props', () => { + it('defaults to a size of xs and flush', () => { + const wrapper = shallow(); + const toggleButton = getToggleButton(wrapper); + + expect(toggleButton.prop('size')).toEqual('xs'); + expect(toggleButton.prop('flush')).toEqual('both'); + }); + + it('passes down custom button props', () => { + const wrapper = shallow(); + const toggleButton = getToggleButton(wrapper); + + expect(toggleButton.prop('size')).toEqual('l'); + }); + }); + + describe('links', () => { + const wrapper = shallowWithIntl(); + const links = wrapper.find(FormattedMessage).dive(); + + it('renders a documentation link', () => { + const docLink = links.find(EuiLink).first(); + + expect(docLink.prop('href')).toContain('/user-interfaces.html'); + }); + + it('renders a link back to the standalone UI', () => { + const switchLink = links.find(EuiLink).last(); + + expect(switchLink.prop('href')).toBe('http://localhost:3002'); + }); + }); +}); + +describe('appendBetaNotificationItem', () => { + const mockSideNav = { + name: 'Hello world', + items: [ + { id: '1', name: 'Link 1' }, + { id: '2', name: 'Link 2' }, + ], + }; + + it('inserts a beta notification into a side nav items array', () => { + appendBetaNotificationItem(mockSideNav); + + expect(mockSideNav).toEqual({ + name: 'Hello world', + items: [ + { id: '1', name: 'Link 1' }, + { id: '2', name: 'Link 2' }, + { + id: 'beta', + name: '', + className: 'betaNotificationSideNavItem', + renderItem: expect.any(Function), + }, + ], + }); + }); + + it('renders the BetaNotification component as a side nav item', () => { + const SideNavItem = (mockSideNav.items[2] as any).renderItem; + const wrapper = shallow(); + + expect(wrapper.hasClass('betaNotificationWrapper')).toBe(true); + expect(wrapper.find(BetaNotification)).toHaveLength(1); + }); + + it('does nothing if no side nav was passed', () => { + const mockEmptySideNav = undefined; + appendBetaNotificationItem(mockEmptySideNav); + + expect(mockEmptySideNav).toEqual(undefined); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx new file mode 100644 index 0000000000000..46aa0a0af9e89 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/beta.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { + EuiPopover, + EuiPopoverTitle, + EuiPopoverFooter, + EuiButtonEmpty, + EuiButtonEmptyProps, + EuiText, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public'; + +import { docLinks } from '../doc_links'; +import { getEnterpriseSearchUrl } from '../enterprise_search_url'; + +import './beta.scss'; + +interface Props { + buttonProps?: EuiButtonEmptyProps; +} + +export const BetaNotification: React.FC = ({ buttonProps }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = () => setIsPopoverOpen((isOpen) => !isOpen); + const closePopover = () => setIsPopoverOpen(false); + + return ( + + {i18n.translate('xpack.enterpriseSearch.beta.buttonLabel', { + defaultMessage: 'This is a beta user interface', + })} + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + anchorPosition="rightDown" + repositionOnScroll + > + + {i18n.translate('xpack.enterpriseSearch.beta.popover.title', { + defaultMessage: 'Enterprise Search in Kibana is a beta user interface', + })} + +
+ +

+ {i18n.translate('xpack.enterpriseSearch.beta.popover.description', { + defaultMessage: + 'The Kibana interface for Enterprise Search is a beta feature. It is subject to change and is not covered by the same level of support as generally available features. This interface will become the sole management panel for Enterprise Search with the 8.0 release. Until then, the standalone Enterprise Search UI remains available and supported.', + })} +

+
+
+ + + Learn more +
+ ), + standaloneUILink: ( + switch to the Enterprise Search UI + ), + }} + /> + + + ); +}; + +export const appendBetaNotificationItem = (sideNav: KibanaPageTemplateProps['solutionNav']) => { + if (sideNav) { + sideNav.items.push({ + id: 'beta', + name: '', + className: 'betaNotificationSideNavItem', + renderItem: () => ( +
+ +
+ ), + }); + } +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index 5b02756e44b52..bc612de884f8b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -7,6 +7,8 @@ import { setMockValues } from '../../__mocks__/kea_logic'; +jest.mock('./beta', () => ({ appendBetaNotificationItem: jest.fn() })); // Mostly adding this to get tests passing. Should be removed once we're out of beta + import React from 'react'; import { shallow } from 'enzyme'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index affec11921545..5da455283eceb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -23,6 +23,8 @@ import { HttpLogic } from '../http'; import { BreadcrumbTrail } from '../kibana_chrome/generate_breadcrumbs'; import { Loading } from '../loading'; +import { appendBetaNotificationItem } from './beta'; + import './page_template.scss'; /* @@ -61,6 +63,8 @@ export const EnterpriseSearchPageTemplate: React.FC = ({ const hasCustomEmptyState = !!emptyState; const showCustomEmptyState = hasCustomEmptyState && isEmptyState; + appendBetaNotificationItem(solutionNav); + return ( Date: Mon, 28 Jun 2021 14:28:10 -0700 Subject: [PATCH 23/74] [eslint/module-migration] add support for re-export defs and test rule (#102840) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../rules/module_migration.js | 9 ++- .../rules/module_migration.test.js | 74 +++++++++++++++++++ packages/kbn-i18n/src/react/index.tsx | 3 +- packages/kbn-i18n/src/react/inject.tsx | 1 + 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 packages/kbn-eslint-plugin-eslint/rules/module_migration.test.js diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js index 7e81680bc2b6e..87a1bae8eac1a 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js @@ -9,7 +9,7 @@ const path = require('path'); const KIBANA_ROOT = path.resolve(__dirname, '../../..'); -function checkModuleNameNode(context, mappings, node) { +function checkModuleNameNode(context, mappings, node, desc = 'Imported') { const mapping = mappings.find( (mapping) => mapping.from === node.value || node.value.startsWith(`${mapping.from}/`) ); @@ -42,7 +42,7 @@ function checkModuleNameNode(context, mappings, node) { } context.report({ - message: `Imported module "${node.value}" should be "${newSource}"`, + message: `${desc} module "${node.value}" should be "${newSource}"`, loc: node.loc, fix(fixer) { return fixer.replaceText(node, `'${newSource}'`); @@ -101,6 +101,11 @@ module.exports = { ImportDeclaration(node) { checkModuleNameNode(context, mappings, node.source); }, + ExportNamedDeclaration(node) { + if (node.source) { + checkModuleNameNode(context, mappings, node.source, 'Re-exported'); + } + }, CallExpression(node) { if ( node.callee.type === 'Identifier' && diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.test.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.test.js new file mode 100644 index 0000000000000..d89c8df213994 --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.test.js @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const { RuleTester } = require('eslint'); +const rule = require('./module_migration'); +const dedent = require('dedent'); + +const ruleTester = new RuleTester({ + parser: require.resolve('babel-eslint'), + parserOptions: { + ecmaVersion: 2018, + }, +}); + +ruleTester.run('@kbn/eslint/module-migration', rule, { + valid: [ + { + code: dedent` + import "bar" + require('bar') + export { foo } from "bar" + export const foo2 = 'bar' + `, + + options: [ + [ + { + from: 'foo', + to: 'bar', + }, + ], + ], + }, + ], + + invalid: [ + { + code: dedent` + import "foo" + require('foo/foo2') + export { foo } from 'foo' + export const foo2 = 'bar' + `, + + options: [ + [ + { + from: 'foo', + to: 'bar', + }, + ], + ], + errors: [ + { + line: 1, + message: 'Imported module "foo" should be "bar"', + }, + { + line: 2, + message: 'Imported module "foo/foo2" should be "bar/foo2"', + }, + { + line: 3, + message: 'Re-exported module "foo" should be "bar"', + }, + ], + }, + ], +}); diff --git a/packages/kbn-i18n/src/react/index.tsx b/packages/kbn-i18n/src/react/index.tsx index bc0a164d412af..a6d8ed17d3b66 100644 --- a/packages/kbn-i18n/src/react/index.tsx +++ b/packages/kbn-i18n/src/react/index.tsx @@ -8,6 +8,7 @@ // eslint-disable-next-line @kbn/eslint/module_migration import { InjectedIntl as _InjectedIntl, InjectedIntlProps as _InjectedIntlProps } from 'react-intl'; +// eslint-disable-next-line @kbn/eslint/module_migration export type { InjectedIntl, InjectedIntlProps } from 'react-intl'; export { @@ -20,7 +21,7 @@ export { FormattedMessage, FormattedHTMLMessage, // Only used for testing. Use I18nProvider otherwise. - IntlProvider as __IntlProvider, + IntlProvider as __IntlProvider, // eslint-disable-next-line @kbn/eslint/module_migration } from 'react-intl'; export { I18nProvider } from './provider'; diff --git a/packages/kbn-i18n/src/react/inject.tsx b/packages/kbn-i18n/src/react/inject.tsx index a76d5e5110748..dec12bc5dd03b 100644 --- a/packages/kbn-i18n/src/react/inject.tsx +++ b/packages/kbn-i18n/src/react/inject.tsx @@ -12,4 +12,5 @@ * More docs and examples can be found here https://github.com/yahoo/react-intl/wiki/API#injection-api */ +// eslint-disable-next-line @kbn/eslint/module_migration export { injectIntl as injectI18n } from 'react-intl'; From 899ee5e0de46829ec9a26a673f308b474fe24d30 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Mon, 28 Jun 2021 16:34:20 -0500 Subject: [PATCH 24/74] [Fleet] Add multi field support to preconfiguration API (#103347) * [Fleet] Add multi field support to preconfiguration API * Loosen preconfig value schema --- x-pack/plugins/fleet/server/services/preconfiguration.ts | 7 ++++++- .../plugins/fleet/server/types/models/preconfiguration.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index e016fafe5459d..622f056ace214 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -373,6 +373,11 @@ function deepMergeVars( throw new Error(name); } const originalVar = original.vars[name]; - Reflect.set(original.vars, name, { ...originalVar, ...val }); + const newVar = + // If a single value was passed in to a multi field, ensure it gets converted to a multi + Array.isArray(originalVar.value) && !Array.isArray(val.value) + ? { ...val, value: [val.value] } + : val; + Reflect.set(original.vars, name, { ...originalVar, ...newVar }); } } diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index e988283c4aad9..4ea9f086bda68 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -23,7 +23,7 @@ const varsSchema = schema.maybe( schema.object({ name: schema.string(), type: schema.maybe(schema.string()), - value: schema.maybe(schema.oneOf([schema.string(), schema.number()])), + value: schema.maybe(schema.any()), frozen: schema.maybe(schema.boolean()), }) ) From 196531502f8cea18e484e05205fcb47990617300 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Mon, 28 Jun 2021 18:06:54 -0400 Subject: [PATCH 25/74] Sharing saved objects phase 3.5 (#100424) --- .../core/public/kibana-plugin-core-public.md | 2 + ...resolvedsimplesavedobject.aliastargetid.md | 13 + ...n-core-public.resolvedsimplesavedobject.md | 22 ++ ...ublic.resolvedsimplesavedobject.outcome.md | 15 + ...c.resolvedsimplesavedobject.savedobject.md | 13 + .../kibana-plugin-core-public.savedobject.md | 2 +- ...ugin-core-public.savedobject.namespaces.md | 2 +- ...a-plugin-core-public.savedobjectsclient.md | 1 + ...-core-public.savedobjectsclient.resolve.md | 13 + ...vedobjectsresolveresponse.aliastargetid.md | 13 + ...core-public.savedobjectsresolveresponse.md | 21 ++ ...lic.savedobjectsresolveresponse.outcome.md | 15 + ...avedobjectsresolveresponse.saved_object.md | 13 + ...-public.simplesavedobject._constructor_.md | 4 +- ...na-plugin-core-public.simplesavedobject.md | 3 +- ...ore-public.simplesavedobject.namespaces.md | 13 + .../kibana-plugin-core-server.savedobject.md | 2 +- ...ugin-core-server.savedobject.namespaces.md | 2 +- ...core-server.savedobjectsresolveresponse.md | 2 +- ...avedobjectsresolveresponse.saved_object.md | 2 + src/core/public/index.ts | 2 + src/core/public/public.api.md | 18 +- src/core/public/saved_objects/index.ts | 2 + .../saved_objects_client.test.ts | 58 +++ .../saved_objects/saved_objects_client.ts | 27 ++ .../saved_objects_service.mock.ts | 1 + .../saved_objects/simple_saved_object.ts | 7 + src/core/public/saved_objects/types.ts | 37 ++ .../core_usage_data_service.mock.ts | 6 + .../core_usage_data_service.test.ts | 19 + .../core_usage_data_service.ts | 66 +++- .../core_usage_stats_client.ts | 13 + src/core/server/core_usage_data/index.ts | 3 +- src/core/server/core_usage_data/types.ts | 12 + .../object_types/registration.ts | 1 + .../service/lib/repository.test.js | 40 +- .../saved_objects/service/lib/repository.ts | 59 ++- .../service/saved_objects_client.ts | 3 + src/core/server/server.api.md | 17 +- src/core/types/saved_objects.ts | 5 +- .../collectors/core/core_usage_collector.ts | 60 +++ .../public/lib/bulk_get_objects.ts | 20 + .../public/lib/find_objects.ts | 10 - .../public/lib/index.ts | 3 +- .../object_view/saved_object_view.tsx | 30 +- .../components/delete_confirm_modal.test.tsx | 49 ++- .../components/delete_confirm_modal.tsx | 44 ++- .../objects_table/components/table.tsx | 9 +- .../objects_table/saved_objects_table.tsx | 97 +++-- .../public/services/types/action.ts | 2 +- .../server/routes/{get.ts => bulk_get.ts} | 36 +- .../server/routes/index.test.ts | 8 +- .../server/routes/index.ts | 4 +- src/plugins/spaces_oss/public/api.ts | 23 +- src/plugins/telemetry/schema/oss_plugins.json | 58 +++ .../apis/saved_objects_management/bulk_get.ts | 83 +++++ .../apis/saved_objects_management/get.ts | 47 --- .../apis/saved_objects_management/index.ts | 2 +- .../saved_objects_management/bulk_get.ts | 93 +++++ .../saved_objects_management/get.ts | 53 --- .../saved_objects_management/index.ts | 2 +- .../job_spaces_list/job_spaces_list.tsx | 6 +- .../authorization/authorization_service.tsx | 1 + x-pack/plugins/security/server/mocks.ts | 2 + x-pack/plugins/security/server/plugin.test.ts | 2 + x-pack/plugins/security/server/plugin.ts | 4 + .../saved_objects/ensure_authorized.test.ts | 49 ++- .../server/saved_objects/ensure_authorized.ts | 23 ++ .../security/server/saved_objects/index.ts | 13 + .../secure_saved_objects_client_wrapper.ts | 20 +- ...secure_spaces_client_wrapper.test.mocks.ts | 17 + .../secure_spaces_client_wrapper.test.ts | 124 ++++++- .../spaces/secure_spaces_client_wrapper.ts | 97 ++++- .../server/spaces/setup_spaces_client.ts | 4 +- x-pack/plugins/spaces/common/index.ts | 7 +- x-pack/plugins/spaces/common/types.ts | 18 + .../components/selectable_spaces_control.tsx | 3 +- .../components/alias_table.tsx | 108 ++++++ .../components/relatives_footer.tsx | 46 +++ .../components/selectable_spaces_control.tsx | 105 +++--- .../components/share_mode_control.scss | 3 - .../components/share_mode_control.tsx | 175 +++++---- .../share_to_space_flyout_internal.scss | 3 + .../share_to_space_flyout_internal.test.tsx | 349 +++++++++++------- .../share_to_space_flyout_internal.tsx | 256 ++++++++----- .../components/share_to_space_form.tsx | 6 +- .../components/types.ts | 16 + .../share_saved_objects_to_space_action.tsx | 9 +- .../space_avatar/space_avatar_internal.tsx | 6 + .../public/space_list/space_list_internal.tsx | 5 +- .../spaces_manager/spaces_manager.mock.ts | 1 + .../spaces_manager/spaces_manager.test.ts | 29 ++ .../public/spaces_manager/spaces_manager.ts | 28 +- .../suspense_error_boundary.tsx | 11 +- .../spaces/public/ui_api/components.tsx | 12 +- .../spaces/public/ui_api/lazy_wrapper.tsx | 4 +- x-pack/plugins/spaces/server/index.ts | 7 +- .../__fixtures__/create_mock_so_repository.ts | 1 + .../disable_legacy_url_aliases.test.ts | 143 +++++++ .../external/disable_legacy_url_aliases.ts | 50 +++ .../server/routes/api/external/index.ts | 2 + .../spaces_client/spaces_client.mock.ts | 1 + .../spaces_client/spaces_client.test.ts | 21 ++ .../server/spaces_client/spaces_client.ts | 23 +- .../spaces_usage_collector.test.ts | 1 + .../spaces_usage_collector.ts | 6 + .../spaces/server/usage_stats/types.ts | 1 + .../usage_stats/usage_stats_client.mock.ts | 1 + .../usage_stats/usage_stats_client.test.ts | 27 +- .../server/usage_stats/usage_stats_client.ts | 7 + .../schema/xpack_plugins.json | 6 + .../translations/translations/ja-JP.json | 9 - .../translations/translations/zh-CN.json | 7 - .../suites/disable_legacy_url_aliases.ts | 128 +++++++ .../apis/disable_legacy_url_aliases.ts | 80 ++++ .../security_and_spaces/apis/index.ts | 1 + .../apis/disable_legacy_url_aliases.ts | 45 +++ .../spaces_only/apis/index.ts | 1 + 118 files changed, 2792 insertions(+), 640 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.resolve.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md create mode 100644 docs/development/core/public/kibana-plugin-core-public.simplesavedobject.namespaces.md create mode 100644 src/core/public/saved_objects/types.ts create mode 100644 src/plugins/saved_objects_management/public/lib/bulk_get_objects.ts rename src/plugins/saved_objects_management/server/routes/{get.ts => bulk_get.ts} (52%) create mode 100644 test/api_integration/apis/saved_objects_management/bulk_get.ts delete mode 100644 test/api_integration/apis/saved_objects_management/get.ts create mode 100644 test/plugin_functional/test_suites/saved_objects_management/bulk_get.ts delete mode 100644 test/plugin_functional/test_suites/saved_objects_management/get.ts create mode 100644 x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts create mode 100644 x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx create mode 100644 x-pack/plugins/spaces/public/share_saved_objects_to_space/components/relatives_footer.tsx delete mode 100644 x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.scss create mode 100644 x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.scss create mode 100644 x-pack/plugins/spaces/public/share_saved_objects_to_space/components/types.ts create mode 100644 x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts create mode 100644 x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts create mode 100644 x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts create mode 100644 x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts create mode 100644 x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index f341a7cd9315f..a13438ff48e0b 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -98,6 +98,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [OverlayStart](./kibana-plugin-core-public.overlaystart.md) | | | [Plugin](./kibana-plugin-core-public.plugin.md) | The interface that should be returned by a PluginInitializer. | | [PluginInitializerContext](./kibana-plugin-core-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | +| [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) | This interface is a very simple wrapper for SavedObjects resolved from the server with the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md). | | [SavedObject](./kibana-plugin-core-public.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-core-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectError](./kibana-plugin-core-public.savedobjecterror.md) | | @@ -126,6 +127,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsImportUnknownError](./kibana-plugin-core-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-core-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | | [SavedObjectsMigrationVersion](./kibana-plugin-core-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | +| [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) | | | [SavedObjectsStart](./kibana-plugin-core-public.savedobjectsstart.md) | | | [SavedObjectsUpdateOptions](./kibana-plugin-core-public.savedobjectsupdateoptions.md) | | | [ToastOptions](./kibana-plugin-core-public.toastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) APIs. | diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md new file mode 100644 index 0000000000000..415681b2bb0d3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [aliasTargetId](./kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md) + +## ResolvedSimpleSavedObject.aliasTargetId property + +The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`. + +Signature: + +```typescript +aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId']; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md new file mode 100644 index 0000000000000..43727d86296a4 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) + +## ResolvedSimpleSavedObject interface + +This interface is a very simple wrapper for SavedObjects resolved from the server with the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md). + +Signature: + +```typescript +export interface ResolvedSimpleSavedObject +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aliasTargetId](./kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md) | SavedObjectsResolveResponse['aliasTargetId'] | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | +| [outcome](./kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md) | SavedObjectsResolveResponse['outcome'] | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* 'aliasMatch' -- One document with a legacy URL alias matched the given ID; in this case the saved_object.id field is different than the given ID. \* 'conflict' -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the saved_object object is the exact match, and the saved_object.id field is the same as the given ID. | +| [savedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md) | SimpleSavedObject<T> | The saved object that was found. | + diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md new file mode 100644 index 0000000000000..ceeef7706cc0f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [outcome](./kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md) + +## ResolvedSimpleSavedObject.outcome property + +The outcome for a successful `resolve` call is one of the following values: + +\* `'exactMatch'` -- One document exactly matched the given ID. \* `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different than the given ID. \* `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + +Signature: + +```typescript +outcome: SavedObjectsResolveResponse['outcome']; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md new file mode 100644 index 0000000000000..c05e8801768c9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [savedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md) + +## ResolvedSimpleSavedObject.savedObject property + +The saved object that was found. + +Signature: + +```typescript +savedObject: SimpleSavedObject; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobject.md b/docs/development/core/public/kibana-plugin-core-public.savedobject.md index 9404927f94957..26f472b741268 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobject.md @@ -19,7 +19,7 @@ export interface SavedObject | [error](./kibana-plugin-core-public.savedobject.error.md) | SavedObjectError | | | [id](./kibana-plugin-core-public.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-core-public.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [namespaces](./kibana-plugin-core-public.savedobject.namespaces.md) | string[] | Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. | +| [namespaces](./kibana-plugin-core-public.savedobject.namespaces.md) | string[] | Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with namespaceType: 'agnostic'. | | [originId](./kibana-plugin-core-public.savedobject.originid.md) | string | The ID of the saved object this originated from. This is set if this object's id was regenerated; that can happen during migration from a legacy single-namespace type, or during import. It is only set during migration or create operations. This is used during import to ensure that ID regeneration is deterministic, so saved objects will be overwritten if they are imported multiple times into a given space. | | [references](./kibana-plugin-core-public.savedobject.references.md) | SavedObjectReference[] | A reference to another saved object. | | [type](./kibana-plugin-core-public.savedobject.type.md) | string | The type of Saved Object. Each plugin can define it's own custom Saved Object types. | diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobject.namespaces.md b/docs/development/core/public/kibana-plugin-core-public.savedobject.namespaces.md index 257df45934506..3418b964ab2d7 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobject.namespaces.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobject.namespaces.md @@ -4,7 +4,7 @@ ## SavedObject.namespaces property -Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. +Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with `namespaceType: 'agnostic'`. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.md index 96bbeae346b2e..aacda031003c6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.md @@ -26,6 +26,7 @@ The constructor for this class is marked as internal. Third-party code should no | [delete](./kibana-plugin-core-public.savedobjectsclient.delete.md) | | (type: string, id: string, options?: SavedObjectsDeleteOptions | undefined) => ReturnType<SavedObjectsApi['delete']> | Deletes an object | | [find](./kibana-plugin-core-public.savedobjectsclient.find.md) | | <T = unknown, A = unknown>(options: SavedObjectsFindOptions) => Promise<SavedObjectsFindResponsePublic<T, unknown>> | Search for objects | | [get](./kibana-plugin-core-public.savedobjectsclient.get.md) | | <T = unknown>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | +| [resolve](./kibana-plugin-core-public.savedobjectsclient.resolve.md) | | <T = unknown>(type: string, id: string) => Promise<ResolvedSimpleSavedObject<T>> | Resolves a single object | ## Methods diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.resolve.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.resolve.md new file mode 100644 index 0000000000000..15fb1f3e9ac22 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsclient.resolve.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) > [resolve](./kibana-plugin-core-public.savedobjectsclient.resolve.md) + +## SavedObjectsClient.resolve property + +Resolves a single object + +Signature: + +```typescript +resolve: (type: string, id: string) => Promise>; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md new file mode 100644 index 0000000000000..02055da686880 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) > [aliasTargetId](./kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md) + +## SavedObjectsResolveResponse.aliasTargetId property + +The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`. + +Signature: + +```typescript +aliasTargetId?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md new file mode 100644 index 0000000000000..4345f2949d48e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) + +## SavedObjectsResolveResponse interface + + +Signature: + +```typescript +export interface SavedObjectsResolveResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aliasTargetId](./kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | +| [outcome](./kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md) | 'exactMatch' | 'aliasMatch' | 'conflict' | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* 'aliasMatch' -- One document with a legacy URL alias matched the given ID; in this case the saved_object.id field is different than the given ID. \* 'conflict' -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the saved_object object is the exact match, and the saved_object.id field is the same as the given ID. | +| [saved\_object](./kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | The saved object that was found. | + diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md new file mode 100644 index 0000000000000..ff4367d804e5d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) > [outcome](./kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md) + +## SavedObjectsResolveResponse.outcome property + +The outcome for a successful `resolve` call is one of the following values: + +\* `'exactMatch'` -- One document exactly matched the given ID. \* `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different than the given ID. \* `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + +Signature: + +```typescript +outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md new file mode 100644 index 0000000000000..d8a74d766d582 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) > [saved\_object](./kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md) + +## SavedObjectsResolveResponse.saved\_object property + +The saved object that was found. + +Signature: + +```typescript +saved_object: SavedObject; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md index 8fb005421e870..c73a3a200cc24 100644 --- a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `SimpleSavedObject` class Signature: ```typescript -constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, }: SavedObjectType); +constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, namespaces, }: SavedObjectType); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(client: SavedObjectsClientContract, { id, type, version, attributes, | Parameter | Type | Description | | --- | --- | --- | | client | SavedObjectsClientContract | | -| { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, } | SavedObjectType<T> | | +| { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, namespaces, } | SavedObjectType<T> | | diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md index 35264a3a4cf0c..e15a4d4ea6d09 100644 --- a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.md @@ -18,7 +18,7 @@ export declare class SimpleSavedObject | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(client, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, })](./kibana-plugin-core-public.simplesavedobject._constructor_.md) | | Constructs a new instance of the SimpleSavedObject class | +| [(constructor)(client, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, namespaces, })](./kibana-plugin-core-public.simplesavedobject._constructor_.md) | | Constructs a new instance of the SimpleSavedObject class | ## Properties @@ -30,6 +30,7 @@ export declare class SimpleSavedObject | [error](./kibana-plugin-core-public.simplesavedobject.error.md) | | SavedObjectType<T>['error'] | | | [id](./kibana-plugin-core-public.simplesavedobject.id.md) | | SavedObjectType<T>['id'] | | | [migrationVersion](./kibana-plugin-core-public.simplesavedobject.migrationversion.md) | | SavedObjectType<T>['migrationVersion'] | | +| [namespaces](./kibana-plugin-core-public.simplesavedobject.namespaces.md) | | SavedObjectType<T>['namespaces'] | Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with namespaceType: 'agnostic'. | | [references](./kibana-plugin-core-public.simplesavedobject.references.md) | | SavedObjectType<T>['references'] | | | [type](./kibana-plugin-core-public.simplesavedobject.type.md) | | SavedObjectType<T>['type'] | | diff --git a/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.namespaces.md b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.namespaces.md new file mode 100644 index 0000000000000..7fb0a4e3a717a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.simplesavedobject.namespaces.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SimpleSavedObject](./kibana-plugin-core-public.simplesavedobject.md) > [namespaces](./kibana-plugin-core-public.simplesavedobject.namespaces.md) + +## SimpleSavedObject.namespaces property + +Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with `namespaceType: 'agnostic'`. + +Signature: + +```typescript +namespaces: SavedObjectType['namespaces']; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobject.md index 07172487e6fde..4c62b359b284d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobject.md @@ -19,7 +19,7 @@ export interface SavedObject | [error](./kibana-plugin-core-server.savedobject.error.md) | SavedObjectError | | | [id](./kibana-plugin-core-server.savedobject.id.md) | string | The ID of this Saved Object, guaranteed to be unique for all objects of the same type | | [migrationVersion](./kibana-plugin-core-server.savedobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [namespaces](./kibana-plugin-core-server.savedobject.namespaces.md) | string[] | Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. | +| [namespaces](./kibana-plugin-core-server.savedobject.namespaces.md) | string[] | Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with namespaceType: 'agnostic'. | | [originId](./kibana-plugin-core-server.savedobject.originid.md) | string | The ID of the saved object this originated from. This is set if this object's id was regenerated; that can happen during migration from a legacy single-namespace type, or during import. It is only set during migration or create operations. This is used during import to ensure that ID regeneration is deterministic, so saved objects will be overwritten if they are imported multiple times into a given space. | | [references](./kibana-plugin-core-server.savedobject.references.md) | SavedObjectReference[] | A reference to another saved object. | | [type](./kibana-plugin-core-server.savedobject.type.md) | string | The type of Saved Object. Each plugin can define it's own custom Saved Object types. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobject.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobject.namespaces.md index 2a555db01df3b..3c2909486219b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobject.namespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobject.namespaces.md @@ -4,7 +4,7 @@ ## SavedObject.namespaces property -Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. +Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with `namespaceType: 'agnostic'`. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md index ffcf15dbc80c7..8a2504ec7adcc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md @@ -17,5 +17,5 @@ export interface SavedObjectsResolveResponse | --- | --- | --- | | [aliasTargetId](./kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | | [outcome](./kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md) | 'exactMatch' | 'aliasMatch' | 'conflict' | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* 'aliasMatch' -- One document with a legacy URL alias matched the given ID; in this case the saved_object.id field is different than the given ID. \* 'conflict' -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the saved_object object is the exact match, and the saved_object.id field is the same as the given ID. | -| [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | | +| [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | The saved object that was found. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md index c184312675f75..c7748a2f97025 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md @@ -4,6 +4,8 @@ ## SavedObjectsResolveResponse.saved\_object property +The saved object that was found. + Signature: ```typescript diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 32737ff427ef3..9bf1a05abc34e 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -99,6 +99,7 @@ export type { } from './application'; export { SimpleSavedObject } from './saved_objects'; +export type { ResolvedSimpleSavedObject } from './saved_objects'; export type { SavedObjectsBatchResponse, SavedObjectsBulkCreateObject, @@ -107,6 +108,7 @@ export type { SavedObjectsBulkUpdateOptions, SavedObjectsCreateOptions, SavedObjectsFindResponsePublic, + SavedObjectsResolveResponse, SavedObjectsUpdateOptions, SavedObject, SavedObjectAttribute, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 32897f10425d6..8b87c21e22fa4 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1118,6 +1118,13 @@ export type ResolveDeprecationResponse = { reason: string; }; +// @public +export interface ResolvedSimpleSavedObject { + aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId']; + outcome: SavedObjectsResolveResponse['outcome']; + savedObject: SimpleSavedObject; +} + // Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1247,6 +1254,7 @@ export class SavedObjectsClient { // Warning: (ae-forgotten-export) The symbol "SavedObjectsFindOptions" needs to be exported by the entry point index.d.ts find: (options: SavedObjectsFindOptions_2) => Promise>; get: (type: string, id: string) => Promise>; + resolve: (type: string, id: string) => Promise>; update(type: string, id: string, attributes: T, { version, references, upsert }?: SavedObjectsUpdateOptions): Promise>; } @@ -1467,6 +1475,13 @@ export interface SavedObjectsMigrationVersion { // @public export type SavedObjectsNamespaceType = 'single' | 'multiple' | 'multiple-isolated' | 'agnostic'; +// @public (undocumented) +export interface SavedObjectsResolveResponse { + aliasTargetId?: string; + outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; + saved_object: SavedObject; +} + // @public (undocumented) export interface SavedObjectsStart { // (undocumented) @@ -1504,7 +1519,7 @@ export class ScopedHistory implements History { - constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, }: SavedObject); + constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion, coreMigrationVersion, namespaces, }: SavedObject); // (undocumented) attributes: T; // (undocumented) @@ -1521,6 +1536,7 @@ export class SimpleSavedObject { id: SavedObject['id']; // (undocumented) migrationVersion: SavedObject['migrationVersion']; + namespaces: SavedObject['namespaces']; // (undocumented) references: SavedObject['references']; // (undocumented) diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index cd75bc16f8362..bd22947b174b7 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -17,9 +17,11 @@ export type { SavedObjectsCreateOptions, SavedObjectsFindResponsePublic, SavedObjectsUpdateOptions, + SavedObjectsResolveResponse, SavedObjectsBulkUpdateOptions, } from './saved_objects_client'; export { SimpleSavedObject } from './simple_saved_object'; +export type { ResolvedSimpleSavedObject } from './types'; export type { SavedObjectsStart } from './saved_objects_service'; export type { SavedObjectsBaseOptions, diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index c2beef5b990c1..85441b9841eaf 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import type { SavedObjectsResolveResponse } from 'src/core/server'; + import { SavedObjectsClient } from './saved_objects_client'; import { SimpleSavedObject } from './simple_saved_object'; import { httpServiceMock } from '../http/http_service.mock'; @@ -147,6 +149,62 @@ describe('SavedObjectsClient', () => { }); }); + describe('#resolve', () => { + beforeEach(() => { + beforeEach(() => { + http.fetch.mockResolvedValue({ + saved_object: doc, + outcome: 'conflict', + aliasTargetId: 'another-id', + } as SavedObjectsResolveResponse); + }); + }); + + test('rejects if `type` is undefined', async () => { + expect(savedObjectsClient.resolve(undefined as any, doc.id)).rejects.toMatchInlineSnapshot( + `[Error: requires type and id]` + ); + }); + + test('rejects if `id` is undefined', async () => { + expect(savedObjectsClient.resolve(doc.type, undefined as any)).rejects.toMatchInlineSnapshot( + `[Error: requires type and id]` + ); + }); + + test('makes HTTP call', () => { + savedObjectsClient.resolve(doc.type, doc.id); + expect(http.fetch.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/saved_objects/resolve/config/AVwSwFxtcMV38qjDZoQg", + Object { + "body": undefined, + "method": undefined, + "query": undefined, + }, + ], + ] + `); + }); + + test('rejects when HTTP call fails', async () => { + http.fetch.mockRejectedValueOnce(new Error('Request failed')); + await expect(savedObjectsClient.resolve(doc.type, doc.id)).rejects.toMatchInlineSnapshot( + `[Error: Request failed]` + ); + }); + + test('resolves with ResolvedSimpleSavedObject instance', async () => { + const result = await savedObjectsClient.resolve(doc.type, doc.id); + expect(result.savedObject).toBeInstanceOf(SimpleSavedObject); + expect(result.savedObject.type).toBe(doc.type); + expect(result.savedObject.get('title')).toBe('Example title'); + expect(result.outcome).toBe('conflict'); + expect(result.aliasTargetId).toBe('another-id'); + }); + }); + describe('#delete', () => { beforeEach(() => { http.fetch.mockResolvedValue({}); diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 36ec3e734bd96..838b7adebc897 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -16,11 +16,15 @@ import { SavedObjectsClientContract as SavedObjectsApi, SavedObjectsFindOptions as SavedObjectFindOptionsServer, SavedObjectsMigrationVersion, + SavedObjectsResolveResponse, } from '../../server'; import { SimpleSavedObject } from './simple_saved_object'; +import type { ResolvedSimpleSavedObject } from './types'; import { HttpFetchOptions, HttpSetup } from '../http'; +export type { SavedObjectsResolveResponse }; + type PromiseType> = T extends Promise ? U : never; type SavedObjectsFindOptions = Omit< @@ -421,6 +425,29 @@ export class SavedObjectsClient { return request; } + /** + * Resolves a single object + * + * @param {string} type + * @param {string} id + * @returns The resolve result for the saved object for the given type and id. + */ + public resolve = ( + type: string, + id: string + ): Promise> => { + if (!type || !id) { + return Promise.reject(new Error('requires type and id')); + } + + const path = `${this.getPath(['resolve'])}/${type}/${id}`; + const request: Promise> = this.savedObjectsFetch(path, {}); + return request.then(({ saved_object: object, outcome, aliasTargetId }) => { + const savedObject = new SimpleSavedObject(this, object); + return { savedObject, outcome, aliasTargetId }; + }); + }; + /** * Updates an object * diff --git a/src/core/public/saved_objects/saved_objects_service.mock.ts b/src/core/public/saved_objects/saved_objects_service.mock.ts index 625ea6b5dd2da..2ceef1c077c39 100644 --- a/src/core/public/saved_objects/saved_objects_service.mock.ts +++ b/src/core/public/saved_objects/saved_objects_service.mock.ts @@ -18,6 +18,7 @@ const createStartContractMock = () => { bulkGet: jest.fn(), find: jest.fn(), get: jest.fn(), + resolve: jest.fn(), update: jest.fn(), }, }; diff --git a/src/core/public/saved_objects/simple_saved_object.ts b/src/core/public/saved_objects/simple_saved_object.ts index b78890893c4ce..449d3d7943fca 100644 --- a/src/core/public/saved_objects/simple_saved_object.ts +++ b/src/core/public/saved_objects/simple_saved_object.ts @@ -30,6 +30,11 @@ export class SimpleSavedObject { public coreMigrationVersion: SavedObjectType['coreMigrationVersion']; public error: SavedObjectType['error']; public references: SavedObjectType['references']; + /** + * Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with + * `namespaceType: 'agnostic'`. + */ + public namespaces: SavedObjectType['namespaces']; constructor( private client: SavedObjectsClientContract, @@ -42,6 +47,7 @@ export class SimpleSavedObject { references, migrationVersion, coreMigrationVersion, + namespaces, }: SavedObjectType ) { this.id = id; @@ -51,6 +57,7 @@ export class SimpleSavedObject { this._version = version; this.migrationVersion = migrationVersion; this.coreMigrationVersion = coreMigrationVersion; + this.namespaces = namespaces; if (error) { this.error = error; } diff --git a/src/core/public/saved_objects/types.ts b/src/core/public/saved_objects/types.ts new file mode 100644 index 0000000000000..ac3df16730125 --- /dev/null +++ b/src/core/public/saved_objects/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectsResolveResponse } from '../../server'; +import { SimpleSavedObject } from './simple_saved_object'; + +/** + * This interface is a very simple wrapper for SavedObjects resolved from the server + * with the {@link SavedObjectsClient}. + * + * @public + */ +export interface ResolvedSimpleSavedObject { + /** + * The saved object that was found. + */ + savedObject: SimpleSavedObject; + /** + * The outcome for a successful `resolve` call is one of the following values: + * + * * `'exactMatch'` -- One document exactly matched the given ID. + * * `'aliasMatch'` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different + * than the given ID. + * * `'conflict'` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the + * `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID. + */ + outcome: SavedObjectsResolveResponse['outcome']; + /** + * The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`. + */ + aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId']; +} diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts index 5fa67fecb2a8a..a03f79096004b 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts @@ -139,6 +139,12 @@ const createStartContractMock = () => { storeSizeBytes: 1, }, ], + legacyUrlAliases: { + inactiveCount: 1, + activeCount: 1, + disabledCount: 1, + totalCount: 3, + }, }, }, }) diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index 95dd392016c17..2395d6d1c1725 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -183,6 +183,19 @@ describe('CoreUsageDataService', () => { }, ], } as any); + elasticsearch.client.asInternalUser.search.mockResolvedValueOnce({ + body: { + hits: { total: { value: 6 } }, + aggregations: { + aliases: { + buckets: { + active: { doc_count: 1 }, + disabled: { doc_count: 2 }, + }, + }, + }, + }, + } as any); const typeRegistry = savedObjectsServiceMock.createTypeRegistryMock(); typeRegistry.getAllTypes.mockReturnValue([ { name: 'type 1', indexPattern: '.kibana' }, @@ -329,6 +342,12 @@ describe('CoreUsageDataService', () => { "storeSizeBytes": 2000, }, ], + "legacyUrlAliases": Object { + "activeCount": 1, + "disabledCount": 2, + "inactiveCount": 3, + "totalCount": 6, + }, }, }, } diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index afe1b45175f86..7cf38dddc563e 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -13,6 +13,11 @@ import { hasConfigPathIntersection, ChangedDeprecatedPaths } from '@kbn/config'; import { CoreService } from 'src/core/types'; import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server'; +import { + AggregationsFiltersAggregate, + AggregationsFiltersBucketItem, + SearchTotalHits, +} from '@elastic/elasticsearch/api/types'; import { CoreContext } from '../core_context'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; import { HttpConfigType, InternalHttpServiceSetup } from '../http'; @@ -29,6 +34,7 @@ import { isConfigured } from './is_configured'; import { ElasticsearchServiceStart } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; import { coreUsageStatsType } from './core_usage_stats'; +import { LEGACY_URL_ALIAS_TYPE } from '../saved_objects/object_types'; import { CORE_USAGE_STATS_TYPE } from './constants'; import { CoreUsageStatsClient } from './core_usage_stats_client'; import { MetricsServiceSetup, OpsMetrics } from '..'; @@ -98,11 +104,25 @@ export class CoreUsageDataService implements CoreService { - const indices = await Promise.all( + const [indices, legacyUrlAliases] = await Promise.all([ + this.getSavedObjectIndicesUsageData(savedObjects, elasticsearch), + this.getSavedObjectAliasUsageData(elasticsearch), + ]); + return { + indices, + legacyUrlAliases, + }; + } + + private async getSavedObjectIndicesUsageData( + savedObjects: SavedObjectsServiceStart, + elasticsearch: ElasticsearchServiceStart + ) { + return Promise.all( Array.from( savedObjects .getTypeRegistry() @@ -136,10 +156,44 @@ export class CoreUsageDataService implements CoreService; + const disabledCount = buckets.disabled.doc_count as number; + const activeCount = buckets.active.doc_count as number; + const inactiveCount = totalCount - disabledCount - activeCount; + + return { totalCount, disabledCount, activeCount, inactiveCount }; } private async getCoreUsageData( @@ -162,7 +216,7 @@ export class CoreUsageDataService implements CoreService { }, }); + /** Each time resolve is called, usage stats are incremented depending upon the outcome. */ + const expectIncrementCounter = (n, outcomeStatString) => { + expect(client.update).toHaveBeenNthCalledWith( + n, + expect.objectContaining({ + body: expect.objectContaining({ + upsert: expect.objectContaining({ + [CORE_USAGE_STATS_TYPE]: { + [outcomeStatString]: 1, + [REPOSITORY_RESOLVE_OUTCOME_STATS.TOTAL]: 1, + }, + }), + }), + }), + expect.anything() + ); + }; + describe('outcomes', () => { describe('error', () => { const expectNotFoundError = async (type, id, options) => { @@ -3302,9 +3321,10 @@ describe('SavedObjectsRepository', () => { ); await expectNotFoundError(type, id, options); - expect(client.update).not.toHaveBeenCalled(); + expect(client.update).toHaveBeenCalledTimes(1); // incremented stats expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target expect(client.mget).not.toHaveBeenCalled(); + expectIncrementCounter(1, REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND); }); it('because actual object and alias object are both not found', async () => { @@ -3320,9 +3340,10 @@ describe('SavedObjectsRepository', () => { ); await expectNotFoundError(type, id, options); - expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.update).toHaveBeenCalledTimes(2); // retrieved alias object, then incremented stats expect(client.get).not.toHaveBeenCalled(); expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expectIncrementCounter(2, REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND); }); }); @@ -3335,9 +3356,10 @@ describe('SavedObjectsRepository', () => { ); const result = await savedObjectsRepository.resolve(type, id, options); - expect(client.update).not.toHaveBeenCalled(); + expect(client.update).toHaveBeenCalledTimes(1); // incremented stats expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target expect(client.mget).not.toHaveBeenCalled(); + expectIncrementCounter(1, REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); expect(result).toEqual({ saved_object: expect.objectContaining({ type, id }), outcome: 'exactMatch', @@ -3354,9 +3376,10 @@ describe('SavedObjectsRepository', () => { ); const result = await savedObjectsRepository.resolve(type, id, options); - expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.update).toHaveBeenCalledTimes(2); // retrieved alias object, then incremented stats expect(client.get).toHaveBeenCalledTimes(1); // retrieved actual target expect(client.mget).not.toHaveBeenCalled(); + expectIncrementCounter(2, REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); expect(result).toEqual({ saved_object: expect.objectContaining({ type, id }), outcome: 'exactMatch', @@ -3388,9 +3411,10 @@ describe('SavedObjectsRepository', () => { ); const result = await savedObjectsRepository.resolve(type, id, options); - expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.update).toHaveBeenCalledTimes(2); // retrieved alias object, then incremented stats expect(client.get).not.toHaveBeenCalled(); expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expectIncrementCounter(2, REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); expect(result).toEqual({ saved_object: expect.objectContaining({ type, id }), outcome: 'exactMatch', @@ -3429,9 +3453,10 @@ describe('SavedObjectsRepository', () => { ); const result = await savedObjectsRepository.resolve(type, id, options); - expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.update).toHaveBeenCalledTimes(2); // retrieved alias object, then incremented stats expect(client.get).not.toHaveBeenCalled(); expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expectIncrementCounter(2, REPOSITORY_RESOLVE_OUTCOME_STATS.ALIAS_MATCH); expect(result).toEqual({ saved_object: expect.objectContaining({ type, id: aliasTargetId }), outcome: 'aliasMatch', @@ -3470,9 +3495,10 @@ describe('SavedObjectsRepository', () => { ); const result = await savedObjectsRepository.resolve(type, id, options); - expect(client.update).toHaveBeenCalledTimes(1); // retrieved alias object + expect(client.update).toHaveBeenCalledTimes(2); // retrieved alias object, then incremented stats expect(client.get).not.toHaveBeenCalled(); expect(client.mget).toHaveBeenCalledTimes(1); // retrieved actual target and alias target + expectIncrementCounter(2, REPOSITORY_RESOLVE_OUTCOME_STATS.CONFLICT); expect(result).toEqual({ saved_object: expect.objectContaining({ type, id }), outcome: 'conflict', diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index c9fa50da55df1..986467c917dd2 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -8,6 +8,11 @@ import { omit, isObject } from 'lodash'; import type { estypes } from '@elastic/elasticsearch'; +import { + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + REPOSITORY_RESOLVE_OUTCOME_STATS, +} from '../../../core_usage_data'; import type { ElasticsearchClient } from '../../../elasticsearch/'; import type { Logger } from '../../../logging'; import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; @@ -1057,7 +1062,7 @@ export class SavedObjectsRepository { const time = this._getCurrentTime(); // retrieve the alias, and if it is not disabled, update it - const aliasResponse = await this.client.update<{ 'legacy-url-alias': LegacyUrlAlias }>( + const aliasResponse = await this.client.update<{ [LEGACY_URL_ALIAS_TYPE]: LegacyUrlAlias }>( { id: rawAliasId, index: this.getIndexForType(LEGACY_URL_ALIAS_TYPE), @@ -1128,21 +1133,25 @@ export class SavedObjectsRepository { // @ts-expect-error MultiGetHit._source is optional aliasMatchDoc.found && this.rawDocExistsInNamespace(aliasMatchDoc, namespace); + let result: SavedObjectsResolveResponse | null = null; + let outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND; if (foundExactMatch && foundAliasMatch) { - return { + result = { // @ts-expect-error MultiGetHit._source is optional saved_object: getSavedObjectFromSource(this._registry, type, id, exactMatchDoc), outcome: 'conflict', aliasTargetId: legacyUrlAlias.targetId, }; + outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.CONFLICT; } else if (foundExactMatch) { - return { + result = { // @ts-expect-error MultiGetHit._source is optional saved_object: getSavedObjectFromSource(this._registry, type, id, exactMatchDoc), outcome: 'exactMatch', }; + outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH; } else if (foundAliasMatch) { - return { + result = { saved_object: getSavedObjectFromSource( this._registry, type, @@ -1153,6 +1162,13 @@ export class SavedObjectsRepository { outcome: 'aliasMatch', aliasTargetId: legacyUrlAlias.targetId, }; + outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.ALIAS_MATCH; + } + + await this.incrementResolveOutcomeStats(outcomeStatString); + + if (result !== null) { + return result; } throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -1649,8 +1665,8 @@ export class SavedObjectsRepository { type: string, id: string, counterFields: Array, - options: SavedObjectsIncrementCounterOptions = {} - ): Promise> { + options?: SavedObjectsIncrementCounterOptions + ) { if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); } @@ -1671,6 +1687,16 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } + return this.incrementCounterInternal(type, id, counterFields, options); + } + + /** @internal incrementCounter function that is used interally and bypasses validation checks. */ + private async incrementCounterInternal( + type: string, + id: string, + counterFields: Array, + options: SavedObjectsIncrementCounterOptions = {} + ): Promise> { const { migrationVersion, refresh = DEFAULT_REFRESH_SETTING, @@ -2064,8 +2090,25 @@ export class SavedObjectsRepository { id: string, options: SavedObjectsBaseOptions ): Promise> { - const object = await this.get(type, id, options); - return { saved_object: object, outcome: 'exactMatch' }; + try { + const object = await this.get(type, id, options); + await this.incrementResolveOutcomeStats(REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); + return { saved_object: object, outcome: 'exactMatch' }; + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + await this.incrementResolveOutcomeStats(REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND); + } + throw err; + } + } + + private async incrementResolveOutcomeStats(outcomeStatString: string) { + await this.incrementCounterInternal( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [outcomeStatString, REPOSITORY_RESOLVE_OUTCOME_STATS.TOTAL], + { refresh: false } + ).catch(() => {}); // if the call fails for some reason, intentionally swallow the error } private validateInitialNamespaces(type: string, initialNamespaces: string[] | undefined) { diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 1423050145695..abb86d8120a9b 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -311,6 +311,9 @@ export interface SavedObjectsUpdateResponse * @public */ export interface SavedObjectsResolveResponse { + /** + * The saved object that was found. + */ saved_object: SavedObject; /** * The outcome for a successful `resolve` call is one of the following values: diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3bc0b54635eb5..13ec594df9075 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -503,6 +503,12 @@ export interface CoreServicesUsageData { storeSizeBytes: number; primaryStoreSizeBytes: number; }[]; + legacyUrlAliases: { + activeCount: number; + inactiveCount: number; + disabledCount: number; + totalCount: number; + }; }; } @@ -765,6 +771,16 @@ export interface CoreUsageStats { 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsUpdate.total'?: number; + // (undocumented) + 'savedObjectsRepository.resolvedOutcome.aliasMatch'?: number; + // (undocumented) + 'savedObjectsRepository.resolvedOutcome.conflict'?: number; + // (undocumented) + 'savedObjectsRepository.resolvedOutcome.exactMatch'?: number; + // (undocumented) + 'savedObjectsRepository.resolvedOutcome.notFound'?: number; + // (undocumented) + 'savedObjectsRepository.resolvedOutcome.total'?: number; } // @public (undocumented) @@ -2925,7 +2941,6 @@ export interface SavedObjectsResolveImportErrorsOptions { export interface SavedObjectsResolveResponse { aliasTargetId?: string; outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; - // (undocumented) saved_object: SavedObject; } diff --git a/src/core/types/saved_objects.ts b/src/core/types/saved_objects.ts index 416b562b175b6..3a97c2fd6f010 100644 --- a/src/core/types/saved_objects.ts +++ b/src/core/types/saved_objects.ts @@ -84,7 +84,10 @@ export interface SavedObject { migrationVersion?: SavedObjectsMigrationVersion; /** A semver value that is used when upgrading objects between Kibana versions. */ coreMigrationVersion?: string; - /** Namespace(s) that this saved object exists in. This attribute is only used for multi-namespace saved object types. */ + /** + * Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with + * `namespaceType: 'agnostic'`. + */ namespaces?: string[]; /** * The ID of the saved object this originated from. This is set if this object's `id` was regenerated; that can happen during migration diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index bf51e21bb9bf4..ffc4559876b2b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -377,6 +377,34 @@ export function getCoreUsageCollector( }, }, }, + legacyUrlAliases: { + inactiveCount: { + type: 'long', + _meta: { + description: + 'Count of legacy URL aliases that are inactive; they are not disabled, but they have not been resolved.', + }, + }, + activeCount: { + type: 'long', + _meta: { + description: + 'Count of legacy URL aliases that are active; they are not disabled, and they have been resolved at least once.', + }, + }, + disabledCount: { + type: 'long', + _meta: { + description: 'Count of legacy URL aliases that are disabled.', + }, + }, + totalCount: { + type: 'long', + _meta: { + description: 'Total count of legacy URL aliases.', + }, + }, + }, }, }, // Saved Objects Client APIs @@ -914,6 +942,38 @@ export function getCoreUsageCollector( description: 'How many times this API has been called without all types selected.', }, }, + // Saved Objects Repository counters + 'savedObjectsRepository.resolvedOutcome.exactMatch': { + type: 'long', + _meta: { + description: 'How many times a saved object has resolved with an exact match outcome.', + }, + }, + 'savedObjectsRepository.resolvedOutcome.aliasMatch': { + type: 'long', + _meta: { + description: 'How many times a saved object has resolved with an alias match outcome.', + }, + }, + 'savedObjectsRepository.resolvedOutcome.conflict': { + type: 'long', + _meta: { + description: 'How many times a saved object has resolved with a conflict outcome.', + }, + }, + 'savedObjectsRepository.resolvedOutcome.notFound': { + type: 'long', + _meta: { + description: 'How many times a saved object has resolved with a not found outcome.', + }, + }, + 'savedObjectsRepository.resolvedOutcome.total': { + type: 'long', + _meta: { + description: + 'How many times a saved object has resolved with any of the four possible outcomes.', + }, + }, }, fetch() { return getCoreUsageDataService().getCoreUsageData(); diff --git a/src/plugins/saved_objects_management/public/lib/bulk_get_objects.ts b/src/plugins/saved_objects_management/public/lib/bulk_get_objects.ts new file mode 100644 index 0000000000000..1d29a39b5c7e4 --- /dev/null +++ b/src/plugins/saved_objects_management/public/lib/bulk_get_objects.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpStart } from 'src/core/public'; +import { SavedObjectWithMetadata } from '../types'; + +export async function bulkGetObjects( + http: HttpStart, + objects: Array<{ type: string; id: string }> +): Promise { + return await http.post( + `/api/kibana/management/saved_objects/_bulk_get`, + { body: JSON.stringify(objects) } + ); +} diff --git a/src/plugins/saved_objects_management/public/lib/find_objects.ts b/src/plugins/saved_objects_management/public/lib/find_objects.ts index 6bbe8331d129d..9dafae0be303f 100644 --- a/src/plugins/saved_objects_management/public/lib/find_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/find_objects.ts @@ -35,13 +35,3 @@ export async function findObjects( return keysToCamelCaseShallow(response) as SavedObjectsFindResponse; } - -export async function findObject( - http: HttpStart, - type: string, - id: string -): Promise { - return await http.get( - `/api/kibana/management/saved_objects/${encodeURIComponent(type)}/${encodeURIComponent(id)}` - ); -} diff --git a/src/plugins/saved_objects_management/public/lib/index.ts b/src/plugins/saved_objects_management/public/lib/index.ts index ecd070c4b3e87..df1485bedfc69 100644 --- a/src/plugins/saved_objects_management/public/lib/index.ts +++ b/src/plugins/saved_objects_management/public/lib/index.ts @@ -30,7 +30,8 @@ export { FailedImport, } from './process_import_response'; export { getDefaultTitle } from './get_default_title'; -export { findObjects, findObject } from './find_objects'; +export { findObjects } from './find_objects'; +export { bulkGetObjects } from './bulk_get_objects'; export { extractExportDetails, SavedObjectsExportResultDetails } from './extract_export_details'; export { createFieldList } from './create_field_list'; export { getAllowedTypes } from './get_allowed_types'; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 764c493be3ed4..3bf70de1abdad 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -19,7 +19,7 @@ import { } from '../../../../../core/public'; import { ISavedObjectsManagementServiceRegistry } from '../../services'; import { Header, NotFoundErrors, Intro, Form } from './components'; -import { canViewInApp, findObject } from '../../lib'; +import { canViewInApp, bulkGetObjects } from '../../lib'; import { SubmittedFormData } from '../types'; import { SavedObjectWithMetadata } from '../../types'; @@ -41,6 +41,11 @@ interface SavedObjectEditionState { object?: SavedObjectWithMetadata; } +const unableFindSavedObjectNotificationMessage = i18n.translate( + 'savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage', + { defaultMessage: 'Unable to find saved object' } +); + export class SavedObjectEdition extends Component< SavedObjectEditionProps, SavedObjectEditionState @@ -58,13 +63,26 @@ export class SavedObjectEdition extends Component< } componentDidMount() { - const { http, id } = this.props; + const { http, id, notifications } = this.props; const { type } = this.state; - findObject(http, type, id).then((object) => { - this.setState({ - object, + bulkGetObjects(http, [{ type, id }]) + .then(([object]) => { + if (object.error) { + const { message } = object.error; + notifications.toasts.addDanger({ + title: unableFindSavedObjectNotificationMessage, + text: message, + }); + } else { + this.setState({ object }); + } + }) + .catch((err) => { + notifications.toasts.addDanger({ + title: unableFindSavedObjectNotificationMessage, + text: err.message ?? 'Unknown error', + }); }); - }); } render() { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx index f33b5488aa760..1dcf15bc551d4 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx @@ -7,15 +7,21 @@ */ import React from 'react'; +import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl } from '@kbn/test/jest'; import { SavedObjectWithMetadata } from '../../../../common'; import { DeleteConfirmModal } from './delete_confirm_modal'; -const createObject = (): SavedObjectWithMetadata => ({ +interface CreateObjectOptions { + namespaces?: string[]; +} + +const createObject = ({ namespaces }: CreateObjectOptions = {}): SavedObjectWithMetadata => ({ id: 'foo', type: 'bar', attributes: {}, references: [], + namespaces, meta: {}, }); @@ -83,4 +89,45 @@ describe('DeleteConfirmModal', () => { expect(onConfirm).toHaveBeenCalledTimes(1); expect(onCancel).not.toHaveBeenCalled(); }); + + describe('shared objects warning', () => { + it('does not display a callout when no objects are shared', () => { + const objs = [ + createObject(), // if for some reason an object has no namespaces array, it does not count as shared + createObject({ namespaces: [] }), // if for some reason an object has an empty namespaces array, it does not count as shared + createObject({ namespaces: ['one-space'] }), // an object in a single space does not count as shared + ]; + const wrapper = mountWithIntl( + + ); + const callout = findTestSubject(wrapper, 'sharedObjectsWarning'); + expect(callout).toHaveLength(0); + }); + + it('displays a callout when one or more objects are shared', () => { + const objs = [ + createObject({ namespaces: ['one-space'] }), // an object in a single space does not count as shared + createObject({ namespaces: ['one-space', 'another-space'] }), // an object in two spaces counts as shared + createObject({ namespaces: ['*'] }), // an object in all spaces counts as shared + ]; + const wrapper = mountWithIntl( + + ); + const callout = findTestSubject(wrapper, 'sharedObjectsWarning'); + expect(callout).toHaveLength(1); + expect(callout.text()).toMatchInlineSnapshot( + `"2 of your saved objects are sharedShared objects are deleted from every space they are in."` + ); + }); + }); }); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx index d589d5a700801..7f1f3adc96d8b 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx @@ -47,8 +47,17 @@ export const DeleteConfirmModal: FC = ({ return selectedObjects.filter((obj) => obj.meta.hiddenType); }, [selectedObjects]); const deletableObjects = useMemo(() => { - return selectedObjects.filter((obj) => !obj.meta.hiddenType); + return selectedObjects + .filter((obj) => !obj.meta.hiddenType) + .map(({ type, id, meta, namespaces = [] }) => { + const { title = '', icon = 'apps' } = meta; + const isShared = namespaces.length > 1 || namespaces.includes('*'); + return { type, id, icon, title, isShared }; + }); }, [selectedObjects]); + const sharedObjectsCount = useMemo(() => { + return deletableObjects.filter((obj) => obj.isShared).length; + }, [deletableObjects]); if (isDeleting) { return ( @@ -93,6 +102,30 @@ export const DeleteConfirmModal: FC = ({ )} + {sharedObjectsCount > 0 && ( + <> + + } + iconType="alert" + color="warning" + > +

+ +

+
+ + + )}

= ({ { defaultMessage: 'Type' } ), width: '50px', - render: (type, object) => ( + render: (type, { icon }) => ( - + ), }, @@ -124,7 +157,7 @@ export const DeleteConfirmModal: FC = ({ ), }, { - field: 'meta.title', + field: 'title', name: i18n.translate( 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', { defaultMessage: 'Title' } @@ -157,7 +190,8 @@ export const DeleteConfirmModal: FC = ({ > diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx index ad61b0b692ea7..4e4bd51c4bb84 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx @@ -48,7 +48,7 @@ export interface TableProps { filterOptions: any[]; capabilities: ApplicationStart['capabilities']; onDelete: () => void; - onActionRefresh: (object: SavedObjectWithMetadata) => void; + onActionRefresh: (objects: Array<{ type: string; id: string }>) => void; onExport: (includeReferencesDeep: boolean) => void; goInspectObject: (obj: SavedObjectWithMetadata) => void; pageIndex: number; @@ -277,10 +277,9 @@ export class Table extends PureComponent { this.setState({ activeAction: undefined, }); - const { refreshOnFinish = () => false } = action; - if (refreshOnFinish()) { - onActionRefresh(object); - } + const { refreshOnFinish = () => [] } = action; + const objectsToRefresh = refreshOnFinish(); + onActionRefresh(objectsToRefresh); }); if (action.euiAction.onClick) { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 42c1220ef5540..5ea433f91d1a6 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -30,7 +30,7 @@ import { fetchExportObjects, fetchExportByTypeAndSearch, findObjects, - findObject, + bulkGetObjects, extractExportDetails, SavedObjectsExportResultDetails, getTagFindReferences, @@ -96,6 +96,14 @@ export interface SavedObjectsTableState { isIncludeReferencesDeepChecked: boolean; } +const unableFindSavedObjectsNotificationMessage = i18n.translate( + 'savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage', + { defaultMessage: 'Unable find saved objects' } +); +const unableFindSavedObjectNotificationMessage = i18n.translate( + 'savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage', + { defaultMessage: 'Unable to find saved object' } +); export class SavedObjectsTable extends Component { private _isMounted = false; @@ -129,13 +137,14 @@ export class SavedObjectsTable extends Component { @@ -188,15 +197,15 @@ export class SavedObjectsTable extends Component { - this.setState({ isSearching: true }, this.debouncedFetchObjects); + fetchAllSavedObjects = () => { + this.setState({ isSearching: true }, this.debouncedFindObjects); }; - fetchSavedObject = (type: string, id: string) => { - this.setState({ isSearching: true }, () => this.debouncedFetchObject(type, id)); + fetchSavedObjects = (objects: Array<{ type: string; id: string }>) => { + this.setState({ isSearching: true }, () => this.debouncedBulkGetObjects(objects)); }; - debouncedFetchObjects = debounce(async () => { + debouncedFindObjects = debounce(async () => { const { activeQuery: query, page, perPage } = this.state; const { notifications, http, allowedTypes, taggingApi } = this.props; const { queryText, visibleTypes, selectedTags } = parseQuery(query); @@ -240,27 +249,45 @@ export class SavedObjectsTable extends Component { + debouncedBulkGetObjects = debounce(async (objects: Array<{ type: string; id: string }>) => { const { notifications, http } = this.props; try { - const resp = await findObject(http, type, id); + const resp = await bulkGetObjects(http, objects); if (!this._isMounted) { return; } + const { map: fetchedObjectsMap, errors: objectErrors } = resp.reduce( + ({ map, errors }, obj) => { + if (obj.error) { + errors.push(obj.error.message); + } else { + map.set(getObjectKey(obj), obj); + } + return { map, errors }; + }, + { map: new Map(), errors: [] as string[] } + ); + + if (objectErrors.length) { + notifications.toasts.addDanger({ + title: unableFindSavedObjectNotificationMessage, + text: objectErrors.join(', '), + }); + } + this.setState(({ savedObjects, filteredItemCount }) => { - const refreshedSavedObjects = savedObjects.map((object) => - object.type === type && object.id === id ? resp : object - ); + // modify the existing objects array, replacing any existing objects with the newly fetched ones + const refreshedSavedObjects = savedObjects.map((obj) => { + const fetchedObject = fetchedObjectsMap.get(getObjectKey(obj)); + return fetchedObject ?? obj; + }); return { savedObjects: refreshedSavedObjects, filteredItemCount, @@ -274,21 +301,25 @@ export class SavedObjectsTable extends Component { - await Promise.all([this.fetchSavedObjects(), this.fetchCounts()]); + refreshAllObjects = async () => { + await Promise.all([this.fetchAllSavedObjects(), this.fetchCounts()]); }; - refreshObject = async ({ type, id }: SavedObjectWithMetadata) => { - await this.fetchSavedObject(type, id); + refreshObjects = async (objects: Array<{ type: string; id: string }>) => { + const currentObjectsSet = this.state.savedObjects.reduce( + (acc, obj) => acc.add(getObjectKey(obj)), + new Set() + ); + const objectsToFetch = objects.filter((obj) => currentObjectsSet.has(getObjectKey(obj))); + if (objectsToFetch.length) { + this.fetchSavedObjects(objectsToFetch); + } }; onSelectionChanged = (selection: SavedObjectWithMetadata[]) => { @@ -305,7 +336,7 @@ export class SavedObjectsTable extends Component { - this.fetchSavedObjects(); + this.fetchAllSavedObjects(); this.fetchCounts(); } ); @@ -320,7 +351,7 @@ export class SavedObjectsTable extends Component { this.hideImportFlyout(); - this.fetchSavedObjects(); + this.fetchAllSavedObjects(); this.fetchCounts(); }; @@ -480,7 +511,7 @@ export class SavedObjectsTable extends Component this.setState({ isShowingExportAllOptionsModal: true })} onImport={this.showImportFlyout} - onRefresh={this.refreshObjects} + onRefresh={this.refreshAllObjects} filteredCount={filteredItemCount} /> @@ -645,7 +676,7 @@ export class SavedObjectsTable extends Component void; render?: (item: SavedObjectsManagementRecord) => any; }; - public refreshOnFinish?: () => boolean; + public refreshOnFinish?: () => Array<{ type: string; id: string }>; private callbacks: Function[] = []; diff --git a/src/plugins/saved_objects_management/server/routes/get.ts b/src/plugins/saved_objects_management/server/routes/bulk_get.ts similarity index 52% rename from src/plugins/saved_objects_management/server/routes/get.ts rename to src/plugins/saved_objects_management/server/routes/bulk_get.ts index 5a48f2f2affa7..c277653b6f350 100644 --- a/src/plugins/saved_objects_management/server/routes/get.ts +++ b/src/plugins/saved_objects_management/server/routes/bulk_get.ts @@ -11,34 +11,42 @@ import { IRouter } from 'src/core/server'; import { injectMetaAttributes } from '../lib'; import { ISavedObjectsManagement } from '../services'; -export const registerGetRoute = ( +export const registerBulkGetRoute = ( router: IRouter, managementServicePromise: Promise ) => { - router.get( + router.post( { - path: '/api/kibana/management/saved_objects/{type}/{id}', + path: '/api/kibana/management/saved_objects/_bulk_get', validate: { - params: schema.object({ - type: schema.string(), - id: schema.string(), - }), + body: schema.arrayOf( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ), }, }, router.handleLegacyErrors(async (context, req, res) => { - const { type, id } = req.params; const managementService = await managementServicePromise; const { getClient, typeRegistry } = context.core.savedObjects; - const includedHiddenTypes = [type].filter( - (entry) => typeRegistry.isHidden(entry) && typeRegistry.isImportableAndExportable(entry) + + const objects = req.body; + const uniqueTypes = objects.reduce((acc, { type }) => acc.add(type), new Set()); + const includedHiddenTypes = Array.from(uniqueTypes).filter( + (type) => typeRegistry.isHidden(type) && typeRegistry.isImportableAndExportable(type) ); const client = getClient({ includedHiddenTypes }); - const findResponse = await client.get(type, id); - - const enhancedSavedObject = injectMetaAttributes(findResponse, managementService); + const response = await client.bulkGet(objects); + const enhancedObjects = response.saved_objects.map((obj) => { + if (!obj.error) { + return injectMetaAttributes(obj, managementService); + } + return obj; + }); - return res.ok({ body: enhancedSavedObject }); + return res.ok({ body: enhancedObjects }); }) ); }; diff --git a/src/plugins/saved_objects_management/server/routes/index.test.ts b/src/plugins/saved_objects_management/server/routes/index.test.ts index 7fbc54cbaf556..3ec6afe1c0bbc 100644 --- a/src/plugins/saved_objects_management/server/routes/index.test.ts +++ b/src/plugins/saved_objects_management/server/routes/index.test.ts @@ -23,8 +23,8 @@ describe('registerRoutes', () => { }); expect(httpSetup.createRouter).toHaveBeenCalledTimes(1); - expect(router.get).toHaveBeenCalledTimes(4); - expect(router.post).toHaveBeenCalledTimes(2); + expect(router.get).toHaveBeenCalledTimes(3); + expect(router.post).toHaveBeenCalledTimes(3); expect(router.get).toHaveBeenCalledWith( expect.objectContaining({ @@ -32,9 +32,9 @@ describe('registerRoutes', () => { }), expect.any(Function) ); - expect(router.get).toHaveBeenCalledWith( + expect(router.post).toHaveBeenCalledWith( expect.objectContaining({ - path: '/api/kibana/management/saved_objects/{type}/{id}', + path: '/api/kibana/management/saved_objects/_bulk_get', }), expect.any(Function) ); diff --git a/src/plugins/saved_objects_management/server/routes/index.ts b/src/plugins/saved_objects_management/server/routes/index.ts index 44453fccf88ed..b5b461575604d 100644 --- a/src/plugins/saved_objects_management/server/routes/index.ts +++ b/src/plugins/saved_objects_management/server/routes/index.ts @@ -9,7 +9,7 @@ import { HttpServiceSetup } from 'src/core/server'; import { ISavedObjectsManagement } from '../services'; import { registerFindRoute } from './find'; -import { registerGetRoute } from './get'; +import { registerBulkGetRoute } from './bulk_get'; import { registerScrollForCountRoute } from './scroll_count'; import { registerScrollForExportRoute } from './scroll_export'; import { registerRelationshipsRoute } from './relationships'; @@ -23,7 +23,7 @@ interface RegisterRouteOptions { export function registerRoutes({ http, managementServicePromise }: RegisterRouteOptions) { const router = http.createRouter(); registerFindRoute(router, managementServicePromise); - registerGetRoute(router, managementServicePromise); + registerBulkGetRoute(router, managementServicePromise); registerScrollForCountRoute(router); registerScrollForExportRoute(router); registerRelationshipsRoute(router, managementServicePromise); diff --git a/src/plugins/spaces_oss/public/api.ts b/src/plugins/spaces_oss/public/api.ts index ddee9c0528ba1..b1b6a16958dbd 100644 --- a/src/plugins/spaces_oss/public/api.ts +++ b/src/plugins/spaces_oss/public/api.ts @@ -168,15 +168,19 @@ export interface ShareToSpaceFlyoutProps { */ behaviorContext?: 'within-space' | 'outside-space'; /** - * Optional handler that is called when the user has saved changes and there are spaces to be added to and/or removed from the object. If - * this is not defined, a default handler will be used that calls `/api/spaces/_update_objects_spaces` and displays a toast indicating - * what occurred. + * Optional handler that is called when the user has saved changes and there are spaces to be added to and/or removed from the object and + * its relatives. If this is not defined, a default handler will be used that calls `/api/spaces/_update_objects_spaces` and displays a + * toast indicating what occurred. */ - changeSpacesHandler?: (spacesToAdd: string[], spacesToRemove: string[]) => Promise; + changeSpacesHandler?: ( + objects: Array<{ type: string; id: string }>, + spacesToAdd: string[], + spacesToRemove: string[] + ) => Promise; /** - * Optional callback when the target object is updated. + * Optional callback when the target object and its relatives are updated. */ - onUpdate?: () => void; + onUpdate?: (updatedObjects: Array<{ type: string; id: string }>) => void; /** * Optional callback when the flyout is closed. */ @@ -288,4 +292,11 @@ export interface SpaceAvatarProps { * Default value is true. */ announceSpaceName?: boolean; + + /** + * Whether or not to render the avatar in a disabled state. + * + * Default value is false. + */ + isDisabled?: boolean; } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index f8edfdcc5c364..56fc7697a4e07 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7188,6 +7188,34 @@ } } } + }, + "legacyUrlAliases": { + "properties": { + "inactiveCount": { + "type": "long", + "_meta": { + "description": "Count of legacy URL aliases that are inactive; they are not disabled, but they have not been resolved." + } + }, + "activeCount": { + "type": "long", + "_meta": { + "description": "Count of legacy URL aliases that are active; they are not disabled, and they have been resolved at least once." + } + }, + "disabledCount": { + "type": "long", + "_meta": { + "description": "Count of legacy URL aliases that are disabled." + } + }, + "totalCount": { + "type": "long", + "_meta": { + "description": "Total count of legacy URL aliases." + } + } + } } } } @@ -7744,6 +7772,36 @@ "_meta": { "description": "How many times this API has been called without all types selected." } + }, + "savedObjectsRepository.resolvedOutcome.exactMatch": { + "type": "long", + "_meta": { + "description": "How many times a saved object has resolved with an exact match outcome." + } + }, + "savedObjectsRepository.resolvedOutcome.aliasMatch": { + "type": "long", + "_meta": { + "description": "How many times a saved object has resolved with an alias match outcome." + } + }, + "savedObjectsRepository.resolvedOutcome.conflict": { + "type": "long", + "_meta": { + "description": "How many times a saved object has resolved with a conflict outcome." + } + }, + "savedObjectsRepository.resolvedOutcome.notFound": { + "type": "long", + "_meta": { + "description": "How many times a saved object has resolved with a not found outcome." + } + }, + "savedObjectsRepository.resolvedOutcome.total": { + "type": "long", + "_meta": { + "description": "How many times a saved object has resolved with any of the four possible outcomes." + } } } }, diff --git a/test/api_integration/apis/saved_objects_management/bulk_get.ts b/test/api_integration/apis/saved_objects_management/bulk_get.ts new file mode 100644 index 0000000000000..35fa03a41770c --- /dev/null +++ b/test/api_integration/apis/saved_objects_management/bulk_get.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { Response } from 'supertest'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + + describe('_bulk_get', () => { + const URL = '/api/kibana/management/saved_objects/_bulk_get'; + const validObject = { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab' }; + const invalidObject = { type: 'wigwags', id: 'foo' }; + + before(() => + kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + after(() => + kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + + function expectSuccess(index: number, { body }: Response) { + const { type, id, meta, error } = body[index]; + expect(type).to.eql(validObject.type); + expect(id).to.eql(validObject.id); + expect(meta).to.not.equal(undefined); + expect(error).to.equal(undefined); + } + + function expectBadRequest(index: number, { body }: Response) { + const { type, id, error } = body[index]; + expect(type).to.eql(invalidObject.type); + expect(id).to.eql(invalidObject.id); + expect(error).to.eql({ + message: `Unsupported saved object type: '${invalidObject.type}': Bad Request`, + statusCode: 400, + error: 'Bad Request', + }); + } + + it('should return 200 for object that exists and inject metadata', async () => + await supertest + .post(URL) + .send([validObject]) + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(1); + expectSuccess(0, response); + })); + + it('should return error for invalid object type', async () => + await supertest + .post(URL) + .send([invalidObject]) + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(1); + expectBadRequest(0, response); + })); + + it('should return mix of successes and errors', async () => + await supertest + .post(URL) + .send([validObject, invalidObject]) + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(2); + expectSuccess(0, response); + expectBadRequest(1, response); + })); + }); +} diff --git a/test/api_integration/apis/saved_objects_management/get.ts b/test/api_integration/apis/saved_objects_management/get.ts deleted file mode 100644 index 3b49a28ca4022..0000000000000 --- a/test/api_integration/apis/saved_objects_management/get.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { Response } from 'supertest'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const kibanaServer = getService('kibanaServer'); - - describe('get', () => { - const existingObject = 'visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab'; - const nonexistentObject = 'wigwags/foo'; - - before(async () => { - await kibanaServer.importExport.load( - 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' - ); - }); - after(async () => { - await kibanaServer.importExport.unload( - 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' - ); - }); - - it('should return 200 for object that exists and inject metadata', async () => - await supertest - .get(`/api/kibana/management/saved_objects/${existingObject}`) - .expect(200) - .then((resp: Response) => { - const { body } = resp; - const { type, id, meta } = body; - expect(type).to.eql('visualization'); - expect(id).to.eql('dd7caf20-9efd-11e7-acb3-3dab96693fab'); - expect(meta).to.not.equal(undefined); - })); - - it('should return 404 for object that does not exist', async () => - await supertest.get(`/api/kibana/management/saved_objects/${nonexistentObject}`).expect(404)); - }); -} diff --git a/test/api_integration/apis/saved_objects_management/index.ts b/test/api_integration/apis/saved_objects_management/index.ts index 3af5699ca0458..208ded1d50706 100644 --- a/test/api_integration/apis/saved_objects_management/index.ts +++ b/test/api_integration/apis/saved_objects_management/index.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('saved objects management apis', () => { loadTestFile(require.resolve('./find')); - loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./bulk_get')); loadTestFile(require.resolve('./relationships')); loadTestFile(require.resolve('./scroll_count')); }); diff --git a/test/plugin_functional/test_suites/saved_objects_management/bulk_get.ts b/test/plugin_functional/test_suites/saved_objects_management/bulk_get.ts new file mode 100644 index 0000000000000..b792df4244e60 --- /dev/null +++ b/test/plugin_functional/test_suites/saved_objects_management/bulk_get.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import type { Response } from 'supertest'; +import type { PluginFunctionalProviderContext } from '../../services'; + +export default function ({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('_bulk_get', () => { + describe('saved objects with hidden type', () => { + before(() => + esArchiver.load( + 'test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects' + ) + ); + after(() => + esArchiver.unload( + 'test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects' + ) + ); + const URL = '/api/kibana/management/saved_objects/_bulk_get'; + const hiddenTypeExportableImportable = { + type: 'test-hidden-importable-exportable', + id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', + }; + const hiddenTypeNonExportableImportable = { + type: 'test-hidden-non-importable-exportable', + id: 'op3767a1-9rcg-53u7-jkb3-3dnb74193awc', + }; + + function expectSuccess(index: number, { body }: Response) { + const { type, id, meta, error } = body[index]; + expect(type).to.eql(hiddenTypeExportableImportable.type); + expect(id).to.eql(hiddenTypeExportableImportable.id); + expect(meta).to.not.equal(undefined); + expect(error).to.equal(undefined); + } + + function expectBadRequest(index: number, { body }: Response) { + const { type, id, error } = body[index]; + expect(type).to.eql(hiddenTypeNonExportableImportable.type); + expect(id).to.eql(hiddenTypeNonExportableImportable.id); + expect(error).to.eql({ + message: `Unsupported saved object type: '${hiddenTypeNonExportableImportable.type}': Bad Request`, + statusCode: 400, + error: 'Bad Request', + }); + } + + it('should return 200 for hidden types that are importableAndExportable', async () => + await supertest + .post(URL) + .send([hiddenTypeExportableImportable]) + .set('kbn-xsrf', 'true') + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(1); + expectSuccess(0, response); + })); + + it('should return error for hidden types that are not importableAndExportable', async () => + await supertest + .post(URL) + .send([hiddenTypeNonExportableImportable]) + .set('kbn-xsrf', 'true') + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(1); + expectBadRequest(0, response); + })); + + it('should return mix of successes and errors', async () => + await supertest + .post(URL) + .send([hiddenTypeExportableImportable, hiddenTypeNonExportableImportable]) + .set('kbn-xsrf', 'true') + .expect(200) + .then((response: Response) => { + expect(response.body).to.have.length(2); + expectSuccess(0, response); + expectBadRequest(1, response); + })); + }); + }); +} diff --git a/test/plugin_functional/test_suites/saved_objects_management/get.ts b/test/plugin_functional/test_suites/saved_objects_management/get.ts deleted file mode 100644 index 6a69aa9146f3e..0000000000000 --- a/test/plugin_functional/test_suites/saved_objects_management/get.ts +++ /dev/null @@ -1,53 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { PluginFunctionalProviderContext } from '../../services'; - -export default function ({ getService }: PluginFunctionalProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('get', () => { - describe('saved objects with hidden type', () => { - before(() => - esArchiver.load( - 'test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects' - ) - ); - after(() => - esArchiver.unload( - 'test/functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects' - ) - ); - const hiddenTypeExportableImportable = - 'test-hidden-importable-exportable/ff3733a0-9fty-11e7-ahb3-3dcb94193fab'; - const hiddenTypeNonExportableImportable = - 'test-hidden-non-importable-exportable/op3767a1-9rcg-53u7-jkb3-3dnb74193awc'; - - it('should return 200 for hidden types that are importableAndExportable', async () => - await supertest - .get(`/api/kibana/management/saved_objects/${hiddenTypeExportableImportable}`) - .set('kbn-xsrf', 'true') - .expect(200) - .then((resp) => { - const { body } = resp; - const { type, id, meta } = body; - expect(type).to.eql('test-hidden-importable-exportable'); - expect(id).to.eql('ff3733a0-9fty-11e7-ahb3-3dcb94193fab'); - expect(meta).to.not.equal(undefined); - })); - - it('should return 404 for hidden types that are not importableAndExportable', async () => - await supertest - .get(`/api/kibana/management/saved_objects/${hiddenTypeNonExportableImportable}`) - .set('kbn-xsrf', 'true') - .expect(404)); - }); - }); -} diff --git a/test/plugin_functional/test_suites/saved_objects_management/index.ts b/test/plugin_functional/test_suites/saved_objects_management/index.ts index edaa819e5ea58..03ac96b9a11f6 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/index.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/index.ts @@ -12,7 +12,7 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) { describe('Saved Objects Management', function () { loadTestFile(require.resolve('./find')); loadTestFile(require.resolve('./scroll_count')); - loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./bulk_get')); loadTestFile(require.resolve('./export_transform')); loadTestFile(require.resolve('./import_warnings')); loadTestFile(require.resolve('./hidden_types')); diff --git a/x-pack/plugins/ml/public/application/components/job_spaces_list/job_spaces_list.tsx b/x-pack/plugins/ml/public/application/components/job_spaces_list/job_spaces_list.tsx index 85d1301fee957..a405f0486430c 100644 --- a/x-pack/plugins/ml/public/application/components/job_spaces_list/job_spaces_list.tsx +++ b/x-pack/plugins/ml/public/application/components/job_spaces_list/job_spaces_list.tsx @@ -37,7 +37,11 @@ export const JobSpacesList: FC = ({ spacesApi, spaceIds, jobId, jobType, const [showFlyout, setShowFlyout] = useState(false); - async function changeSpacesHandler(spacesToAdd: string[], spacesToMaybeRemove: string[]) { + async function changeSpacesHandler( + _objects: Array<{ type: string; id: string }>, // this is ignored because ML jobs do not have references + spacesToAdd: string[], + spacesToMaybeRemove: string[] + ) { // If the user is adding the job to all current and future spaces, don't remove it from any specified spaces const spacesToRemove = spacesToAdd.includes(ALL_SPACES_ID) ? [] : spacesToMaybeRemove; diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index 0777c231ecd89..72f2c9843daec 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -94,6 +94,7 @@ export interface AuthorizationServiceSetup { actions: Actions; checkPrivilegesWithRequest: CheckPrivilegesWithRequest; checkPrivilegesDynamicallyWithRequest: CheckPrivilegesDynamicallyWithRequest; + checkSavedObjectsPrivilegesWithRequest: CheckSavedObjectsPrivilegesWithRequest; mode: AuthorizationMode; } diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index c30fcd8b69604..f1f858a40a465 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -23,6 +23,7 @@ function createSetupMock() { actions: mockAuthz.actions, checkPrivilegesWithRequest: mockAuthz.checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: mockAuthz.checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: mockAuthz.checkSavedObjectsPrivilegesWithRequest, mode: mockAuthz.mode, }, registerSpacesService: jest.fn(), @@ -42,6 +43,7 @@ function createStartMock() { actions: mockAuthz.actions, checkPrivilegesWithRequest: mockAuthz.checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: mockAuthz.checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: mockAuthz.checkSavedObjectsPrivilegesWithRequest, mode: mockAuthz.mode, }, }; diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 574e37fdd1841..2d17e75527c6f 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -101,6 +101,7 @@ describe('Security Plugin', () => { }, "checkPrivilegesDynamicallyWithRequest": [Function], "checkPrivilegesWithRequest": [Function], + "checkSavedObjectsPrivilegesWithRequest": [Function], "mode": Object { "useRbacForRequest": [Function], }, @@ -171,6 +172,7 @@ describe('Security Plugin', () => { }, "checkPrivilegesDynamicallyWithRequest": [Function], "checkPrivilegesWithRequest": [Function], + "checkSavedObjectsPrivilegesWithRequest": [Function], "mode": Object { "useRbacForRequest": [Function], }, diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index d0403c0f170ea..1873ca42324c0 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -328,6 +328,8 @@ export class SecurityPlugin checkPrivilegesWithRequest: this.authorizationSetup.checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: this.authorizationSetup .checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: this.authorizationSetup + .checkSavedObjectsPrivilegesWithRequest, mode: this.authorizationSetup.mode, }, @@ -386,6 +388,8 @@ export class SecurityPlugin checkPrivilegesWithRequest: this.authorizationSetup!.checkPrivilegesWithRequest, checkPrivilegesDynamicallyWithRequest: this.authorizationSetup! .checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: this.authorizationSetup! + .checkSavedObjectsPrivilegesWithRequest, mode: this.authorizationSetup!.mode, }, }); diff --git a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts index 531b547a1f275..b9ab7cef7f15b 100644 --- a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts +++ b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts @@ -11,7 +11,11 @@ import type { CheckSavedObjectsPrivileges } from '../authorization'; import { Actions } from '../authorization'; import type { CheckPrivilegesResponse } from '../authorization/types'; import type { EnsureAuthorizedResult } from './ensure_authorized'; -import { ensureAuthorized, getEnsureAuthorizedActionResult } from './ensure_authorized'; +import { + ensureAuthorized, + getEnsureAuthorizedActionResult, + isAuthorizedForObjectInAllSpaces, +} from './ensure_authorized'; describe('ensureAuthorized', () => { function setupDependencies() { @@ -224,3 +228,46 @@ describe('getEnsureAuthorizedActionResult', () => { expect(result).toEqual({ authorizedSpaces: [] }); }); }); + +describe('isAuthorizedForObjectInAllSpaces', () => { + const typeActionMap: EnsureAuthorizedResult<'action'>['typeActionMap'] = new Map([ + ['type-1', { action: { authorizedSpaces: [], isGloballyAuthorized: true } }], + ['type-2', { action: { authorizedSpaces: ['space-1', 'space-2'] } }], + ['type-3', { action: { authorizedSpaces: [] } }], + // type-4 is not present in the results + ]); + + test('returns true if the user is authorized for the type in the given spaces', () => { + const type1Result = isAuthorizedForObjectInAllSpaces('type-1', 'action', typeActionMap, [ + 'space-1', + 'space-2', + 'space-3', + ]); + expect(type1Result).toBe(true); + + const type2Result = isAuthorizedForObjectInAllSpaces('type-2', 'action', typeActionMap, [ + 'space-1', + 'space-2', + ]); + expect(type2Result).toBe(true); + }); + + test('returns false if the user is not authorized for the type in the given spaces', () => { + const type2Result = isAuthorizedForObjectInAllSpaces('type-2', 'action', typeActionMap, [ + 'space-1', + 'space-2', + 'space-3', // the user is not authorized for this type and action in space-3 + ]); + expect(type2Result).toBe(false); + + const type3Result = isAuthorizedForObjectInAllSpaces('type-3', 'action', typeActionMap, [ + 'space-1', // the user is not authorized for this type and action in any space + ]); + expect(type3Result).toBe(false); + + const type4Result = isAuthorizedForObjectInAllSpaces('type-4', 'action', typeActionMap, [ + 'space-1', // the user is not authorized for this type and action in any space + ]); + expect(type4Result).toBe(false); + }); +}); diff --git a/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts b/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts index 0ce7b5f78f13b..c3457e75f9644 100644 --- a/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts +++ b/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts @@ -135,6 +135,29 @@ export function getEnsureAuthorizedActionResult( return record[action] ?? { authorizedSpaces: [] }; } +/** + * Helper function that, given an `EnsureAuthorizedResult`, ensures that the user is authorized to perform a given action for the given + * object type in the given spaces. + * + * @param {string} objectType the object type to check. + * @param {T} action the action to check. + * @param {EnsureAuthorizedResult['typeActionMap']} typeActionMap the typeActionMap from an EnsureAuthorizedResult. + * @param {string[]} spacesToAuthorizeFor the spaces to check. + */ +export function isAuthorizedForObjectInAllSpaces( + objectType: string, + action: T, + typeActionMap: EnsureAuthorizedResult['typeActionMap'], + spacesToAuthorizeFor: string[] +) { + const actionResult = getEnsureAuthorizedActionResult(objectType, action, typeActionMap); + const { authorizedSpaces, isGloballyAuthorized } = actionResult; + const authorizedSpacesSet = new Set(authorizedSpaces); + return ( + isGloballyAuthorized || spacesToAuthorizeFor.every((space) => authorizedSpacesSet.has(space)) + ); +} + async function checkPrivileges( deps: EnsureAuthorizedDependencies, actions: string | string[], diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index 364f639e9e9a3..b291fa86bbf56 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -24,6 +24,19 @@ interface SetupSavedObjectsParams { getSpacesService(): SpacesService | undefined; } +export type { + EnsureAuthorizedDependencies, + EnsureAuthorizedOptions, + EnsureAuthorizedResult, + EnsureAuthorizedActionResult, +} from './ensure_authorized'; + +export { + ensureAuthorized, + getEnsureAuthorizedActionResult, + isAuthorizedForObjectInAllSpaces, +} from './ensure_authorized'; + export function setupSavedObjects({ legacyAuditLogger, audit, diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index ef3dcac4c064b..a3bd215211983 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -41,7 +41,11 @@ import type { EnsureAuthorizedOptions, EnsureAuthorizedResult, } from './ensure_authorized'; -import { ensureAuthorized, getEnsureAuthorizedActionResult } from './ensure_authorized'; +import { + ensureAuthorized, + getEnsureAuthorizedActionResult, + isAuthorizedForObjectInAllSpaces, +} from './ensure_authorized'; interface SecureSavedObjectsClientWrapperOptions { actions: Actions; @@ -1071,20 +1075,6 @@ function namespaceComparator(a: string, b: string) { return A > B ? 1 : A < B ? -1 : 0; } -function isAuthorizedForObjectInAllSpaces( - objectType: string, - action: T, - typeActionMap: EnsureAuthorizedResult['typeActionMap'], - spacesToAuthorizeFor: string[] -) { - const actionResult = getEnsureAuthorizedActionResult(objectType, action, typeActionMap); - const { authorizedSpaces, isGloballyAuthorized } = actionResult; - const authorizedSpacesSet = new Set(authorizedSpaces); - return ( - isGloballyAuthorized || spacesToAuthorizeFor.every((space) => authorizedSpacesSet.has(space)) - ); -} - function getRedactedSpaces( objectType: string, action: T, diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts new file mode 100644 index 0000000000000..02bd9971f28b8 --- /dev/null +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ensureAuthorized } from '../saved_objects'; + +export const mockEnsureAuthorized = jest.fn() as jest.MockedFunction; + +jest.mock('../saved_objects', () => { + return { + ...jest.requireActual('../saved_objects'), + ensureAuthorized: mockEnsureAuthorized, + }; +}); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index bc7cb727edd80..20a524251bd4a 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -5,15 +5,17 @@ * 2.0. */ +import { mockEnsureAuthorized } from './secure_spaces_client_wrapper.test.mocks'; + import { deepFreeze } from '@kbn/std'; -import type { EcsEventOutcome } from 'src/core/server'; +import type { EcsEventOutcome, SavedObjectsClientContract } from 'src/core/server'; import { SavedObjectsErrorHelpers } from 'src/core/server'; import { httpServerMock } from 'src/core/server/mocks'; -import type { GetAllSpacesPurpose, Space } from '../../../spaces/server'; +import type { GetAllSpacesPurpose, LegacyUrlAliasTarget, Space } from '../../../spaces/server'; import { spacesClientMock } from '../../../spaces/server/mocks'; import type { AuditEvent, AuditLogger } from '../audit'; -import { SpaceAuditAction } from '../audit'; +import { SavedObjectAction, SpaceAuditAction } from '../audit'; import { auditServiceMock } from '../audit/index.mock'; import type { AuthorizationServiceSetup, @@ -22,7 +24,11 @@ import type { import { authorizationMock } from '../authorization/index.mock'; import type { CheckPrivilegesResponse } from '../authorization/types'; import type { LegacySpacesAuditLogger } from './legacy_audit_logger'; -import { SecureSpacesClientWrapper } from './secure_spaces_client_wrapper'; +import { + getAliasId, + LEGACY_URL_ALIAS_TYPE, + SecureSpacesClientWrapper, +} from './secure_spaces_client_wrapper'; interface Opts { securityEnabled?: boolean; @@ -71,12 +77,20 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); const request = httpServerMock.createKibanaRequest(); + + const forbiddenError = new Error('Mock ForbiddenError'); + const errors = ({ + decorateForbiddenError: jest.fn().mockReturnValue(forbiddenError), + // other errors exist but are not needed for these test cases + } as unknown) as jest.Mocked; + const wrapper = new SecureSpacesClientWrapper( baseClient, request, authorization, auditLogger, - legacyAuditLogger + legacyAuditLogger, + errors ); return { authorization, @@ -85,6 +99,7 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { baseClient, auditLogger, legacyAuditLogger, + forbiddenError, }; }; @@ -160,6 +175,10 @@ const expectAuditEvent = ( ); }; +beforeEach(() => { + mockEnsureAuthorized.mockReset(); +}); + describe('SecureSpacesClientWrapper', () => { describe('#getAll', () => { const savedObjects = [ @@ -747,4 +766,99 @@ describe('SecureSpacesClientWrapper', () => { }); }); }); + + describe('#disableLegacyUrlAliases', () => { + const alias1 = { targetSpace: 'space-1', targetType: 'type-1', sourceId: 'id' }; + const alias2 = { targetSpace: 'space-2', targetType: 'type-2', sourceId: 'id' }; + + function expectAuditEvents( + auditLogger: AuditLogger, + aliases: LegacyUrlAliasTarget[], + action: EcsEventOutcome + ) { + aliases.forEach((alias) => { + expectAuditEvent(auditLogger, SavedObjectAction.UPDATE, action, { + type: LEGACY_URL_ALIAS_TYPE, + id: getAliasId(alias), + }); + }); + } + + function expectAuthorizationCheck(targetTypes: string[], targetSpaces: string[]) { + expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); + expect(mockEnsureAuthorized).toHaveBeenCalledWith( + expect.any(Object), // dependencies + targetTypes, // unique types of the alias targets + ['bulk_update'], // actions + targetSpaces, // unique spaces of the alias targets + { requireFullAuthorization: false } + ); + } + + describe('when security is not enabled', () => { + const securityEnabled = false; + + it('delegates to base client without checking authorization', async () => { + const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); + const aliases = [alias1]; + await wrapper.disableLegacyUrlAliases(aliases); + + expect(mockEnsureAuthorized).not.toHaveBeenCalled(); + expectAuditEvents(auditLogger, aliases, 'unknown'); + expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledTimes(1); + expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledWith(aliases); + }); + }); + + describe('when security is enabled', () => { + const securityEnabled = true; + + it('re-throws the error if the authorization check fails', async () => { + const error = new Error('Oh no!'); + mockEnsureAuthorized.mockRejectedValue(error); + const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); + const aliases = [alias1, alias2]; + await expect(() => wrapper.disableLegacyUrlAliases(aliases)).rejects.toThrow(error); + + expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); + expectAuditEvents(auditLogger, aliases, 'failure'); + expect(baseClient.disableLegacyUrlAliases).not.toHaveBeenCalled(); + }); + + it('throws a forbidden error when unauthorized', async () => { + mockEnsureAuthorized.mockResolvedValue({ + status: 'partially_authorized', + typeActionMap: new Map() + .set('type-1', { bulk_update: { authorizedSpaces: ['space-1'] } }) + .set('type-2', { bulk_update: { authorizedSpaces: ['space-1'] } }), // the user is not authorized to bulkUpdate type-2 in space-2, so this will throw a forbidden error + }); + const { wrapper, baseClient, auditLogger, forbiddenError } = setup({ securityEnabled }); + const aliases = [alias1, alias2]; + await expect(() => wrapper.disableLegacyUrlAliases(aliases)).rejects.toThrow( + forbiddenError + ); + + expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); + expectAuditEvents(auditLogger, aliases, 'failure'); + expect(baseClient.disableLegacyUrlAliases).not.toHaveBeenCalled(); + }); + + it('updates the legacy URL aliases when authorized', async () => { + mockEnsureAuthorized.mockResolvedValue({ + status: 'partially_authorized', + typeActionMap: new Map() + .set('type-1', { bulk_update: { authorizedSpaces: ['space-1'] } }) + .set('type-2', { bulk_update: { authorizedSpaces: ['space-2'] } }), + }); + const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); + const aliases = [alias1, alias2]; + await wrapper.disableLegacyUrlAliases(aliases); + + expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); + expectAuditEvents(auditLogger, aliases, 'unknown'); + expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledTimes(1); + expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledWith(aliases); + }); + }); + }); }); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index ab882570ac630..f3d66ac0381eb 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -7,19 +7,22 @@ import Boom from '@hapi/boom'; -import type { KibanaRequest } from 'src/core/server'; +import type { KibanaRequest, SavedObjectsClientContract } from 'src/core/server'; import type { GetAllSpacesOptions, GetAllSpacesPurpose, GetSpaceResult, ISpacesClient, + LegacyUrlAliasTarget, Space, } from '../../../spaces/server'; import type { AuditLogger } from '../audit'; -import { SpaceAuditAction, spaceAuditEvent } from '../audit'; +import { SavedObjectAction, savedObjectEvent, SpaceAuditAction, spaceAuditEvent } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; import type { SecurityPluginSetup } from '../plugin'; +import type { EnsureAuthorizedDependencies, EnsureAuthorizedOptions } from '../saved_objects'; +import { ensureAuthorized, isAuthorizedForObjectInAllSpaces } from '../saved_objects'; import type { LegacySpacesAuditLogger } from './legacy_audit_logger'; const PURPOSE_PRIVILEGE_MAP: Record< @@ -38,6 +41,9 @@ const PURPOSE_PRIVILEGE_MAP: Record< ], }; +/** @internal */ +export const LEGACY_URL_ALIAS_TYPE = 'legacy-url-alias'; + export class SecureSpacesClientWrapper implements ISpacesClient { private readonly useRbac = this.authorization.mode.useRbacForRequest(this.request); @@ -46,7 +52,8 @@ export class SecureSpacesClientWrapper implements ISpacesClient { private readonly request: KibanaRequest, private readonly authorization: AuthorizationServiceSetup, private readonly auditLogger: AuditLogger, - private readonly legacyAuditLogger: LegacySpacesAuditLogger + private readonly legacyAuditLogger: LegacySpacesAuditLogger, + private readonly errors: SavedObjectsClientContract['errors'] ) {} public async getAll({ @@ -277,6 +284,85 @@ export class SecureSpacesClientWrapper implements ISpacesClient { return this.spacesClient.delete(id); } + public async disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]) { + if (this.useRbac) { + try { + const [uniqueSpaces, uniqueTypes, typesAndSpacesMap] = aliases.reduce( + ([spaces, types, typesAndSpaces], { targetSpace, targetType }) => { + const spacesForType = typesAndSpaces.get(targetType) ?? new Set(); + return [ + spaces.add(targetSpace), + types.add(targetType), + typesAndSpaces.set(targetType, spacesForType.add(targetSpace)), + ]; + }, + [new Set(), new Set(), new Map>()] + ); + + const action = 'bulk_update'; + const { typeActionMap } = await this.ensureAuthorizedForSavedObjects( + Array.from(uniqueTypes), + [action], + Array.from(uniqueSpaces), + { requireFullAuthorization: false } + ); + const unauthorizedTypes = new Set(); + for (const type of uniqueTypes) { + const spaces = Array.from(typesAndSpacesMap.get(type)!); + if (!isAuthorizedForObjectInAllSpaces(type, action, typeActionMap, spaces)) { + unauthorizedTypes.add(type); + } + } + if (unauthorizedTypes.size > 0) { + const targetTypes = Array.from(unauthorizedTypes).sort().join(','); + const msg = `Unable to disable aliases for ${targetTypes}`; + throw this.errors.decorateForbiddenError(new Error(msg)); + } + } catch (error) { + aliases.forEach((alias) => { + const id = getAliasId(alias); + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.UPDATE, + savedObject: { type: LEGACY_URL_ALIAS_TYPE, id }, + error, + }) + ); + }); + throw error; + } + } + + aliases.forEach((alias) => { + const id = getAliasId(alias); + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.UPDATE, + outcome: 'unknown', + savedObject: { type: LEGACY_URL_ALIAS_TYPE, id }, + }) + ); + }); + + return this.spacesClient.disableLegacyUrlAliases(aliases); + } + + private async ensureAuthorizedForSavedObjects( + types: string[], + actions: T[], + namespaces: string[], + options?: EnsureAuthorizedOptions + ) { + const ensureAuthorizedDependencies: EnsureAuthorizedDependencies = { + actions: this.authorization.actions, + errors: this.errors, + checkSavedObjectsPrivilegesAsCurrentUser: this.authorization.checkSavedObjectsPrivilegesWithRequest( + this.request + ), + }; + return ensureAuthorized(ensureAuthorizedDependencies, types, actions, namespaces, options); + } + private async ensureAuthorizedGlobally(action: string, method: string, forbiddenMessage: string) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); const { username, hasAllRequested } = await checkPrivileges.globally({ kibana: action }); @@ -312,3 +398,8 @@ export class SecureSpacesClientWrapper implements ISpacesClient { return value !== null; } } + +/** @internal This is only exported for testing purposes. */ +export function getAliasId({ targetSpace, targetType, sourceId }: LegacyUrlAliasTarget) { + return `${targetSpace}:${targetType}:${sourceId}`; +} diff --git a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts index 2344d3e8c69d6..b518a1cff2c0f 100644 --- a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts +++ b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SavedObjectsClient } from '../../../../../src/core/server'; import type { SpacesPluginSetup } from '../../../spaces/server'; import type { AuditServiceSetup } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; @@ -39,7 +40,8 @@ export const setupSpacesClient = ({ audit, authz, spaces }: Deps) => { request, authz, audit.asScoped(request), - spacesAuditLogger + spacesAuditLogger, + SavedObjectsClient.errors ) ); }; diff --git a/x-pack/plugins/spaces/common/index.ts b/x-pack/plugins/spaces/common/index.ts index 9935d8055ec30..003a0c068a166 100644 --- a/x-pack/plugins/spaces/common/index.ts +++ b/x-pack/plugins/spaces/common/index.ts @@ -8,4 +8,9 @@ export { isReservedSpace } from './is_reserved_space'; export { MAX_SPACE_INITIALS, SPACE_SEARCH_COUNT_THRESHOLD, ENTER_SPACE_PATH } from './constants'; export { addSpaceIdToPath, getSpaceIdFromPath } from './lib/spaces_url_parser'; -export type { GetAllSpacesOptions, GetAllSpacesPurpose, GetSpaceResult } from './types'; +export type { + GetAllSpacesOptions, + GetAllSpacesPurpose, + GetSpaceResult, + LegacyUrlAliasTarget, +} from './types'; diff --git a/x-pack/plugins/spaces/common/types.ts b/x-pack/plugins/spaces/common/types.ts index 866d29bf64d5b..55bd1c137f8cf 100644 --- a/x-pack/plugins/spaces/common/types.ts +++ b/x-pack/plugins/spaces/common/types.ts @@ -50,3 +50,21 @@ export interface GetSpaceResult extends Space { */ authorizedPurposes?: Record; } + +/** + * Client interface for interacting with legacy URL aliases. + */ +export interface LegacyUrlAliasTarget { + /** + * The namespace that the object existed in when it was converted. + */ + targetSpace: string; + /** + * The type of the object when it was converted. + */ + targetType: string; + /** + * The original ID of the object, before it was converted. + */ + sourceId: string; +} diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx index e1ecc06935791..2f96646844a35 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx @@ -14,6 +14,7 @@ import React, { lazy, Suspense } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import type { Space } from 'src/plugins/spaces_oss/common'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common'; import { getSpaceAvatarComponent } from '../../space_avatar'; // No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana. @@ -83,7 +84,7 @@ export const SelectableSpacesControl = (props: Props) => { className: 'spcCopyToSpace__spacesList', 'data-test-subj': 'cts-form-space-selector', }} - searchable={options.length > 6} + searchable={options.length > SPACE_SEARCH_COUNT_THRESHOLD} > {(list, search) => { return ( diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx new file mode 100644 index 0000000000000..2a2f2470e0199 --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/alias_table.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiTableComputedColumnType, Pagination } from '@elastic/eui'; +import { + EuiCallOut, + EuiFlexItem, + EuiInMemoryTable, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { lazy, Suspense, useMemo, useState } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getSpaceAvatarComponent } from '../../space_avatar'; +import type { ShareToSpaceTarget } from '../../types'; +import type { InternalLegacyUrlAliasTarget } from './types'; + +// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana. +const LazySpaceAvatar = lazy(() => + getSpaceAvatarComponent().then((component) => ({ default: component })) +); + +interface Props { + spaces: ShareToSpaceTarget[]; + aliasesToDisable: InternalLegacyUrlAliasTarget[]; +} + +export const AliasTable: FunctionComponent = ({ spaces, aliasesToDisable }) => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(5); + + const spacesMap = useMemo( + () => + spaces.reduce( + (acc, space) => acc.set(space.id, space), + new Map() + ), + [spaces] + ); + const filteredAliasesToDisable = useMemo( + () => aliasesToDisable.filter(({ spaceExists }) => spaceExists), + [aliasesToDisable] + ); + const aliasesToDisableCount = filteredAliasesToDisable.length; + const pagination: Pagination = { + pageIndex, + pageSize, + totalItemCount: aliasesToDisableCount, + pageSizeOptions: [5, 10, 15, 20], + }; + + return ( + <> + + } + color="warning" + > + + + + + + + }> + { + const space = spacesMap.get(targetSpace)!; // it's safe to use ! here because we filtered only for aliases that are in spaces that exist + return ; // the whole table is wrapped in a Suspense + }, + sortable: ({ targetSpace }) => targetSpace, + } as EuiTableComputedColumnType, + ]} + sorting={true} + pagination={pagination} + onTableChange={({ page: { index, size } }) => { + setPageIndex(index); + setPageSize(size); + }} + tableLayout="auto" + /> + + + + ); +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/relatives_footer.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/relatives_footer.tsx new file mode 100644 index 0000000000000..ea3f29724e0d5 --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/relatives_footer.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiHorizontalRule, EuiText } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import type { SavedObjectReferenceWithContext } from 'src/core/public'; +import type { ShareToSpaceSavedObjectTarget } from 'src/plugins/spaces_oss/public'; + +interface Props { + savedObjectTarget: ShareToSpaceSavedObjectTarget; + referenceGraph: SavedObjectReferenceWithContext[]; + isDisabled: boolean; +} + +export const RelativesFooter = (props: Props) => { + const { savedObjectTarget, referenceGraph, isDisabled } = props; + + const relativesCount = useMemo(() => { + const { type, id } = savedObjectTarget; + return referenceGraph.filter( + (x) => (x.type !== type || x.id !== id) && x.spaces.length > 0 && !x.isMissing + ).length; + }, [savedObjectTarget, referenceGraph]); + + if (relativesCount > 0) { + return ( + <> + + + + + + ); + } + return null; +}; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx index 876c8d027b2b4..fad819d35e18a 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/selectable_spaces_control.tsx @@ -17,7 +17,6 @@ import { EuiLink, EuiLoadingSpinner, EuiSelectable, - EuiSpacer, EuiText, } from '@elastic/eui'; import React, { lazy, Suspense } from 'react'; @@ -25,6 +24,7 @@ import React, { lazy, Suspense } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common'; import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../../common/constants'; import { DocumentationLinksService } from '../../lib'; import { getSpaceAvatarComponent } from '../../space_avatar'; @@ -106,10 +106,14 @@ export const SelectableSpacesControl = (props: Props) => { .sort(createSpacesComparator(activeSpaceId)) .map((space) => { const checked = selectedSpaceIds.includes(space.id); - const additionalProps = getAdditionalProps(space, activeSpaceId, checked); + const { isAvatarDisabled, ...additionalProps } = getAdditionalProps( + space, + activeSpaceId, + checked + ); return { label: space.name, - prepend: , // wrapped in a Suspense below + prepend: , // wrapped in a Suspense below checked: checked ? 'on' : undefined, ['data-space-id']: space.id, ['data-test-subj']: `sts-space-selector-row-${space.id}`, @@ -140,8 +144,7 @@ export const SelectableSpacesControl = (props: Props) => { docLinks! ).getKibanaPrivilegesDocUrl(); return ( - <> - + { }} /> - + ); }; const getNoSpacesAvailable = () => { if (enableCreateNewSpaceLink && spaces.length < 2) { - return ; + return ( + + + + ); } return null; }; @@ -188,46 +195,52 @@ export const SelectableSpacesControl = (props: Props) => { ); const hiddenSpaces = hiddenCount ? {hiddenSpacesLabel} : null; return ( - - - {selectedSpacesLabel} - - {hiddenSpaces} - - } - fullWidth - > - <> - }> - updateSelectedSpaces(newOptions as SpaceOption[])} - listProps={{ - bordered: true, - rowHeight: ROW_HEIGHT, - className: 'spcShareToSpace__spacesList', - 'data-test-subj': 'sts-form-space-selector', - }} - height={ROW_HEIGHT * 3.5} - searchable={options.length > 6} - > - {(list, search) => { - return ( - <> - {search} - {list} - - ); - }} - - + <> + + + {selectedSpacesLabel} + + {hiddenSpaces} + + } + fullWidth + > + <> + + + + + }> + updateSelectedSpaces(newOptions as SpaceOption[])} + listProps={{ + bordered: true, + rowHeight: ROW_HEIGHT, + className: 'spcShareToSpace__spacesList', + 'data-test-subj': 'sts-form-space-selector', + }} + height="full" + searchable={options.length > SPACE_SEARCH_COUNT_THRESHOLD} + > + {(list, search) => { + return ( + <> + {search} + {list} + + ); + }} + + + {getUnknownSpacesLabel()} {getNoSpacesAvailable()} - - + + ); }; @@ -260,8 +273,10 @@ function getAdditionalProps( if (space.isFeatureDisabled) { return { append: APPEND_FEATURE_IS_DISABLED, + isAvatarDisabled: true, }; } + return {}; } /** diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.scss b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.scss deleted file mode 100644 index 3baa21f68d4f3..0000000000000 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.scss +++ /dev/null @@ -1,3 +0,0 @@ -.euiCheckableCard__children { - width: 100%; // required to expand the contents of EuiCheckableCard to the full width -} diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx index df8d72f7a59de..7151f72583d6a 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_mode_control.tsx @@ -5,11 +5,9 @@ * 2.0. */ -import './share_mode_control.scss'; - import { + EuiButtonGroup, EuiCallOut, - EuiCheckableCard, EuiFlexGroup, EuiFlexItem, EuiIconTip, @@ -40,36 +38,27 @@ interface Props { enableSpaceAgnosticBehavior: boolean; } -function createLabel({ - title, - text, - disabled, - tooltip, -}: { - title: string; - text: string; - disabled: boolean; - tooltip?: string; -}) { - return ( - <> - - - {title} - - {tooltip && ( - - - - )} - - - - {text} - - - ); -} +const buttonGroupLegend = i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.buttonGroupLegend', + { defaultMessage: 'Choose how this is shared' } +); + +const shareToAllSpacesId = 'shareToAllSpacesId'; +const shareToAllSpacesButtonLabel = i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.buttonLabel', + { defaultMessage: 'All spaces' } +); + +const shareToExplicitSpacesId = 'shareToExplicitSpacesId'; +const shareToExplicitSpacesButtonLabel = i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.buttonLabel', + { defaultMessage: 'Select spaces' } +); + +const cannotChangeTooltip = i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotChangeTooltip', + { defaultMessage: 'You need additional privileges to change this option.' } +); export const ShareModeControl = (props: Props) => { const { @@ -90,50 +79,9 @@ export const ShareModeControl = (props: Props) => { const { selectedSpaceIds } = shareOptions; const isGlobalControlChecked = selectedSpaceIds.includes(ALL_SPACES_ID); - const shareToAllSpaces = { - id: 'shareToAllSpaces', - title: i18n.translate('xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.title', { - defaultMessage: 'All spaces', - }), - text: i18n.translate('xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.text', { - defaultMessage: 'Make {objectNoun} available in all current and future spaces.', - values: { objectNoun }, - }), - ...(!canShareToAllSpaces && { - tooltip: isGlobalControlChecked - ? i18n.translate( - 'xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotUncheckTooltip', - { defaultMessage: 'You need additional privileges to change this option.' } - ) - : i18n.translate( - 'xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotCheckTooltip', - { defaultMessage: 'You need additional privileges to use this option.' } - ), - }), - disabled: !canShareToAllSpaces, - }; - const shareToExplicitSpaces = { - id: 'shareToExplicitSpaces', - title: i18n.translate( - 'xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.title', - { defaultMessage: 'Select spaces' } - ), - text: i18n.translate('xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.text', { - defaultMessage: 'Make {objectNoun} available in selected spaces only.', - values: { objectNoun }, - }), - disabled: !canShareToAllSpaces && isGlobalControlChecked, - }; - - const toggleShareOption = (allSpaces: boolean) => { - const updatedSpaceIds = allSpaces - ? [ALL_SPACES_ID, ...selectedSpaceIds] - : selectedSpaceIds.filter((id) => id !== ALL_SPACES_ID); - onChange(updatedSpaceIds); - }; const getPrivilegeWarning = () => { - if (!shareToExplicitSpaces.disabled) { + if (canShareToAllSpaces || !isGlobalControlChecked) { return null; } @@ -180,13 +128,66 @@ export const ShareModeControl = (props: Props) => { <> {getPrivilegeWarning()} - toggleShareOption(false)} - disabled={shareToExplicitSpaces.disabled} - > + { + const updatedSpaceIds = + optionId === shareToAllSpacesId + ? [ALL_SPACES_ID, ...selectedSpaceIds] + : selectedSpaceIds.filter((id) => id !== ALL_SPACES_ID); + onChange(updatedSpaceIds); + }} + legend={buttonGroupLegend} + color="secondary" + isFullWidth={true} + isDisabled={!canShareToAllSpaces} + /> + + + + + + + + {isGlobalControlChecked + ? i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.text', + { + defaultMessage: + 'Make {objectNoun} available in all current and future spaces.', + values: { objectNoun }, + } + ) + : i18n.translate( + 'xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.text', + { + defaultMessage: 'Make {objectNoun} available in selected spaces only.', + values: { objectNoun }, + } + )} + + + {!canShareToAllSpaces && ( + + + + )} + + + + + + { enableCreateNewSpaceLink={enableCreateNewSpaceLink} enableSpaceAgnosticBehavior={enableSpaceAgnosticBehavior} /> - - - toggleShareOption(true)} - disabled={shareToAllSpaces.disabled} - /> + ); }; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.scss b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.scss new file mode 100644 index 0000000000000..5f5014c4a82f3 --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.scss @@ -0,0 +1,3 @@ +.spcShareToSpace__flyoutBodyWrapper { + padding: $euiSizeL; +} diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx index 4ec90b7e3826b..f02cae7674058 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx @@ -5,20 +5,14 @@ * 2.0. */ -import type { EuiCheckableCardProps } from '@elastic/eui'; -import { - EuiCallOut, - EuiCheckableCard, - EuiIconTip, - EuiLoadingSpinner, - EuiSelectable, -} from '@elastic/eui'; +import { EuiCallOut, EuiIconTip, EuiLoadingSpinner, EuiSelectable } from '@elastic/eui'; import Boom from '@hapi/boom'; import { act } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import type { SavedObjectReferenceWithContext } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import type { Space } from 'src/plugins/spaces_oss/common'; @@ -26,7 +20,9 @@ import { ALL_SPACES_ID } from '../../../common/constants'; import { CopyToSpaceFlyoutInternal } from '../../copy_saved_objects_to_space/components/copy_to_space_flyout_internal'; import { getSpacesContextProviderWrapper } from '../../spaces_context'; import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { AliasTable } from './alias_table'; import { NoSpacesAvailable } from './no_spaces_available'; +import { RelativesFooter } from './relatives_footer'; import { SelectableSpacesControl } from './selectable_spaces_control'; import { ShareModeControl } from './share_mode_control'; import { getShareToSpaceFlyoutComponent } from './share_to_space_flyout'; @@ -41,6 +37,7 @@ interface SetupOpts { enableCreateNewSpaceLink?: boolean; behaviorContext?: 'within-space' | 'outside-space'; mockFeatureId?: string; // optional feature ID to use for the SpacesContext + additionalShareableReferences?: SavedObjectReferenceWithContext[]; } const setup = async (opts: SetupOpts = {}) => { @@ -94,6 +91,19 @@ const setup = async (opts: SetupOpts = {}) => { title: 'foo', }; + mockSpacesManager.getShareableReferences.mockResolvedValue({ + objects: [ + { + // this is the result for the saved object target; by default, it has no references + type: savedObjectToShare.type, + id: savedObjectToShare.id, + spaces: savedObjectToShare.namespaces, + inboundReferences: [], + }, + ...(opts.additionalShareableReferences ?? []), + ], + }); + const { getStartServices } = coreMock.createSetup(); const startServices = coreMock.createStart(); startServices.application.capabilities = { @@ -138,6 +148,25 @@ const setup = async (opts: SetupOpts = {}) => { return { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToShare }; }; +function changeSpaceSelection(wrapper: ReactWrapper, selectedSpaces: string[]) { + // Using props callback instead of simulating clicks, because EuiSelectable uses a virtualized list, which isn't easily testable via test + // subjects + const spaceSelector = wrapper.find(SelectableSpacesControl); + act(() => { + spaceSelector.props().onChange(selectedSpaces); + }); + wrapper.update(); +} + +async function clickButton(wrapper: ReactWrapper, button: 'continue' | 'save' | 'copy') { + const buttonNode = findTestSubject(wrapper, `sts-${button}-button`); + await act(async () => { + buttonNode.simulate('click'); + await nextTick(); + wrapper.update(); + }); +} + describe('ShareToSpaceFlyout', () => { it('waits for spaces to load', async () => { const { wrapper } = await setup({ returnBeforeSpacesLoad: true }); @@ -212,12 +241,7 @@ describe('ShareToSpaceFlyout', () => { expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(NoSpacesAvailable)).toHaveLength(0); - const copyButton = findTestSubject(wrapper, 'sts-copy-link'); // this link is only present in the warning callout - - await act(async () => { - copyButton.simulate('click'); - await nextTick(); - }); + await clickButton(wrapper, 'copy'); // this link is only present in the warning callout wrapper.update(); expect(wrapper.find(CopyToSpaceFlyoutInternal)).toHaveLength(1); @@ -288,20 +312,8 @@ describe('ShareToSpaceFlyout', () => { expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(NoSpacesAvailable)).toHaveLength(0); - // Using props callback instead of simulating clicks, - // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - const spaceSelector = wrapper.find(SelectableSpacesControl); - act(() => { - spaceSelector.props().onChange(['space-2', 'space-3']); - }); - - const startButton = findTestSubject(wrapper, 'sts-initiate-button'); - - await act(async () => { - startButton.simulate('click'); - await nextTick(); - wrapper.update(); - }); + changeSpaceSelection(wrapper, ['space-2', 'space-3']); + await clickButton(wrapper, 'save'); expect(mockSpacesManager.updateSavedObjectsSpaces).toHaveBeenCalled(); expect(mockToastNotifications.addError).toHaveBeenCalled(); @@ -320,21 +332,8 @@ describe('ShareToSpaceFlyout', () => { expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(NoSpacesAvailable)).toHaveLength(0); - // Using props callback instead of simulating clicks, - // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - const spaceSelector = wrapper.find(SelectableSpacesControl); - - act(() => { - spaceSelector.props().onChange(['space-1', 'space-2', 'space-3']); - }); - - const startButton = findTestSubject(wrapper, 'sts-initiate-button'); - - await act(async () => { - startButton.simulate('click'); - await nextTick(); - wrapper.update(); - }); + changeSpaceSelection(wrapper, ['space-1', 'space-2', 'space-3']); + await clickButton(wrapper, 'save'); const { type, id } = savedObjectToShare; expect(mockSpacesManager.updateSavedObjectsSpaces).toHaveBeenCalledWith( @@ -361,21 +360,8 @@ describe('ShareToSpaceFlyout', () => { expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(NoSpacesAvailable)).toHaveLength(0); - // Using props callback instead of simulating clicks, - // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - const spaceSelector = wrapper.find(SelectableSpacesControl); - - act(() => { - spaceSelector.props().onChange([]); - }); - - const startButton = findTestSubject(wrapper, 'sts-initiate-button'); - - await act(async () => { - startButton.simulate('click'); - await nextTick(); - wrapper.update(); - }); + changeSpaceSelection(wrapper, []); + await clickButton(wrapper, 'save'); const { type, id } = savedObjectToShare; expect(mockSpacesManager.updateSavedObjectsSpaces).toHaveBeenCalledWith( @@ -402,21 +388,8 @@ describe('ShareToSpaceFlyout', () => { expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); expect(wrapper.find(NoSpacesAvailable)).toHaveLength(0); - // Using props callback instead of simulating clicks, - // because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects - const spaceSelector = wrapper.find(SelectableSpacesControl); - - act(() => { - spaceSelector.props().onChange(['space-2', 'space-3']); - }); - - const startButton = findTestSubject(wrapper, 'sts-initiate-button'); - - await act(async () => { - startButton.simulate('click'); - await nextTick(); - wrapper.update(); - }); + changeSpaceSelection(wrapper, ['space-2', 'space-3']); + await clickButton(wrapper, 'save'); const { type, id } = savedObjectToShare; expect(mockSpacesManager.updateSavedObjectsSpaces).toHaveBeenCalledWith( @@ -430,25 +403,13 @@ describe('ShareToSpaceFlyout', () => { expect(onClose).toHaveBeenCalledTimes(1); }); - describe('correctly renders checkable cards', () => { - function getCheckableCardProps( - wrapper: ReactWrapper> - ) { - const iconTip = wrapper.find(EuiIconTip); + describe('correctly renders share mode control', () => { + function getDescriptionAndWarning(wrapper: ReactWrapper) { + const descriptionNode = findTestSubject(wrapper, 'share-mode-control-description'); + const iconTipNode = wrapper.find(ShareModeControl).find(EuiIconTip); return { - checked: wrapper.prop('checked'), - disabled: wrapper.prop('disabled'), - ...(iconTip.length > 0 && { tooltip: iconTip.prop('content') as string }), - }; - } - function getCheckableCards(wrapper: ReactWrapper) { - return { - explicitSpacesCard: getCheckableCardProps( - wrapper.find('#shareToExplicitSpaces').find(EuiCheckableCard) - ), - allSpacesCard: getCheckableCardProps( - wrapper.find('#shareToAllSpaces').find(EuiCheckableCard) - ), + description: descriptionNode.text(), + isPrivilegeTooltipDisplayed: iconTipNode.length > 0, }; } @@ -458,27 +419,23 @@ describe('ShareToSpaceFlyout', () => { it('and the object is not shared to all spaces', async () => { const namespaces = ['my-active-space']; const { wrapper } = await setup({ canShareToAllSpaces, namespaces }); - const shareModeControl = wrapper.find(ShareModeControl); - const checkableCards = getCheckableCards(shareModeControl); + const { description, isPrivilegeTooltipDisplayed } = getDescriptionAndWarning(wrapper); - expect(checkableCards).toEqual({ - explicitSpacesCard: { checked: true, disabled: false }, - allSpacesCard: { checked: false, disabled: false }, - }); - expect(shareModeControl.find(EuiCallOut)).toHaveLength(0); // "Additional privileges required" callout + expect(description).toMatchInlineSnapshot( + `"Make object available in selected spaces only."` + ); + expect(isPrivilegeTooltipDisplayed).toBe(false); }); it('and the object is shared to all spaces', async () => { const namespaces = [ALL_SPACES_ID]; const { wrapper } = await setup({ canShareToAllSpaces, namespaces }); - const shareModeControl = wrapper.find(ShareModeControl); - const checkableCards = getCheckableCards(shareModeControl); + const { description, isPrivilegeTooltipDisplayed } = getDescriptionAndWarning(wrapper); - expect(checkableCards).toEqual({ - explicitSpacesCard: { checked: false, disabled: false }, - allSpacesCard: { checked: true, disabled: false }, - }); - expect(shareModeControl.find(EuiCallOut)).toHaveLength(0); // "Additional privileges required" callout + expect(description).toMatchInlineSnapshot( + `"Make object available in all current and future spaces."` + ); + expect(isPrivilegeTooltipDisplayed).toBe(false); }); }); @@ -488,35 +445,23 @@ describe('ShareToSpaceFlyout', () => { it('and the object is not shared to all spaces', async () => { const namespaces = ['my-active-space']; const { wrapper } = await setup({ canShareToAllSpaces, namespaces }); - const shareModeControl = wrapper.find(ShareModeControl); - const checkableCards = getCheckableCards(shareModeControl); - - expect(checkableCards).toEqual({ - explicitSpacesCard: { checked: true, disabled: false }, - allSpacesCard: { - checked: false, - disabled: true, - tooltip: 'You need additional privileges to use this option.', - }, - }); - expect(shareModeControl.find(EuiCallOut)).toHaveLength(0); // "Additional privileges required" callout + const { description, isPrivilegeTooltipDisplayed } = getDescriptionAndWarning(wrapper); + + expect(description).toMatchInlineSnapshot( + `"Make object available in selected spaces only."` + ); + expect(isPrivilegeTooltipDisplayed).toBe(true); }); it('and the object is shared to all spaces', async () => { const namespaces = [ALL_SPACES_ID]; const { wrapper } = await setup({ canShareToAllSpaces, namespaces }); - const shareModeControl = wrapper.find(ShareModeControl); - const checkableCards = getCheckableCards(shareModeControl); + const { description, isPrivilegeTooltipDisplayed } = getDescriptionAndWarning(wrapper); - expect(checkableCards).toEqual({ - explicitSpacesCard: { checked: false, disabled: true }, - allSpacesCard: { - checked: true, - disabled: true, - tooltip: 'You need additional privileges to change this option.', - }, - }); - expect(shareModeControl.find(EuiCallOut)).toHaveLength(1); // "Additional privileges required" callout + expect(description).toMatchInlineSnapshot( + `"Make object available in all current and future spaces."` + ); + expect(isPrivilegeTooltipDisplayed).toBe(true); }); }); }); @@ -714,4 +659,152 @@ describe('ShareToSpaceFlyout', () => { }); }); }); + + describe('alias list', () => { + it('shows only aliases for spaces that exist', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ + namespaces, + additionalShareableReferences: [ + // it doesn't matter if aliases are for the saved object target or for references; this is easier to mock + { + type: 'foo', + id: '1', + spaces: namespaces, + inboundReferences: [], + spacesWithMatchingAliases: ['space-1', 'some-space-that-does-not-exist'], // space-1 exists, it is mocked at the top + }, + ], + }); + + changeSpaceSelection(wrapper, ['*']); + await clickButton(wrapper, 'continue'); + + const aliasTable = wrapper.find(AliasTable); + expect(aliasTable.prop('aliasesToDisable')).toEqual([ + { targetType: 'foo', sourceId: '1', targetSpace: 'space-1', spaceExists: true }, + { + // this alias is present, and it will be disabled, but it is not displayed in the table below due to the 'spaceExists' field + targetType: 'foo', + sourceId: '1', + targetSpace: 'some-space-that-does-not-exist', + spaceExists: false, + }, + ]); + expect(aliasTable.find(EuiCallOut).text()).toMatchInlineSnapshot( + `"Legacy URL conflict1 legacy URL will be disabled."` + ); + }); + + it('shows only aliases for selected spaces', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ + namespaces, + additionalShareableReferences: [ + // it doesn't matter if aliases are for the saved object target or for references; this is easier to mock + { + type: 'foo', + id: '1', + spaces: namespaces, + inboundReferences: [], + spacesWithMatchingAliases: ['space-1', 'space-2'], // space-1 and space-2 both exist, they are mocked at the top + }, + ], + }); + + changeSpaceSelection(wrapper, ['space-1']); + await clickButton(wrapper, 'continue'); + + const aliasTable = wrapper.find(AliasTable); + expect(aliasTable.prop('aliasesToDisable')).toEqual([ + { targetType: 'foo', sourceId: '1', targetSpace: 'space-1', spaceExists: true }, + // even though an alias exists for space-2, it will not be disabled, because we aren't sharing to that space + ]); + expect(aliasTable.find(EuiCallOut).text()).toMatchInlineSnapshot( + `"Legacy URL conflict1 legacy URL will be disabled."` + ); + }); + }); + + describe('footer', () => { + it('does not show a description of relatives (references) if there are none', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ namespaces }); + + const relativesControl = wrapper.find(RelativesFooter); + expect(relativesControl.isEmptyRender()).toBe(true); + }); + + it('shows a description of filtered relatives (references)', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ + namespaces, + additionalShareableReferences: [ + // the saved object target is already included in the mock results by default; it will not be counted + { type: 'foo', id: '1', spaces: [], inboundReferences: [] }, // this will not be counted because spaces is empty (it may not be a shareable type) + { type: 'foo', id: '2', spaces: namespaces, inboundReferences: [], isMissing: true }, // this will not be counted because isMissing === true + { type: 'foo', id: '3', spaces: namespaces, inboundReferences: [] }, // this will be counted + ], + }); + + const relativesControl = wrapper.find(RelativesFooter); + expect(relativesControl.isEmptyRender()).toBe(false); + expect(relativesControl.text()).toMatchInlineSnapshot(`"1 related object will also change."`); + }); + + function expectButton(wrapper: ReactWrapper, button: 'save' | 'continue') { + const saveButton = findTestSubject(wrapper, 'sts-save-button'); + const continueButton = findTestSubject(wrapper, 'sts-continue-button'); + expect(saveButton).toHaveLength(button === 'save' ? 1 : 0); + expect(continueButton).toHaveLength(button === 'continue' ? 1 : 0); + } + + it('shows a save button if there are no legacy URL aliases to disable', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ namespaces }); + + changeSpaceSelection(wrapper, ['*']); + expectButton(wrapper, 'save'); + }); + + it('shows a save button if there are legacy URL aliases to disable, but none for existing spaces', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ + namespaces, + additionalShareableReferences: [ + // it doesn't matter if aliases are for the saved object target or for references; this is easier to mock + { + type: 'foo', + id: '1', + spaces: namespaces, + inboundReferences: [], + spacesWithMatchingAliases: ['some-space-that-does-not-exist'], + }, + ], + }); + + changeSpaceSelection(wrapper, ['*']); + expectButton(wrapper, 'save'); + }); + + it('shows a continue button if there are legacy URL aliases to disable for existing spaces', async () => { + const namespaces = ['my-active-space']; // the saved object's current namespaces + const { wrapper } = await setup({ + namespaces, + additionalShareableReferences: [ + // it doesn't matter if aliases are for the saved object target or for references; this is easier to mock + { + type: 'foo', + id: '1', + spaces: namespaces, + inboundReferences: [], + spacesWithMatchingAliases: ['space-1', 'some-space-that-does-not-exist'], // space-1 exists, it is mocked at the top + }, + ], + }); + + changeSpaceSelection(wrapper, ['*']); + expectButton(wrapper, 'continue'); + }); + }); }); diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx index d8fc0f299d8e6..712adeb26bccb 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx @@ -5,18 +5,19 @@ * 2.0. */ +import './share_to_space_flyout_internal.scss'; + import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFlyout, - EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - EuiHorizontalRule, EuiIcon, EuiLoadingSpinner, + EuiSpacer, EuiText, EuiTitle, } from '@elastic/eui'; @@ -24,7 +25,7 @@ import React, { lazy, Suspense, useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { ToastsStart } from 'src/core/public'; +import type { SavedObjectReferenceWithContext, ToastsStart } from 'src/core/public'; import type { ShareToSpaceFlyoutProps, ShareToSpaceSavedObjectTarget, @@ -36,8 +37,11 @@ import { useSpaces } from '../../spaces_context'; import type { SpacesManager } from '../../spaces_manager'; import type { ShareToSpaceTarget } from '../../types'; import type { ShareOptions } from '../types'; +import { AliasTable } from './alias_table'; import { DEFAULT_OBJECT_NOUN } from './constants'; +import { RelativesFooter } from './relatives_footer'; import { ShareToSpaceForm } from './share_to_space_form'; +import type { InternalLegacyUrlAliasTarget } from './types'; // No need to wrap LazyCopyToSpaceFlyout in an error boundary, because the ShareToSpaceFlyoutInternal component itself is only ever used in // a lazy-loaded fashion with an error boundary. @@ -66,45 +70,53 @@ function createDefaultChangeSpacesHandler( spacesManager: SpacesManager, toastNotifications: ToastsStart ) { - return async (spacesToAdd: string[], spacesToRemove: string[]) => { - const { type, id, title } = object; - const objects = [{ type, id }]; + return async ( + objects: Array<{ type: string; id: string }>, + spacesToAdd: string[], + spacesToRemove: string[] + ) => { + const { title } = object; + const objectsToUpdate = objects.map(({ type, id }) => ({ type, id })); // only use 'type' and 'id' fields + const relativesCount = objects.length - 1; const toastTitle = i18n.translate('xpack.spaces.shareToSpace.shareSuccessTitle', { values: { objectNoun: object.noun }, defaultMessage: 'Updated {objectNoun}', description: `Object noun can be plural or singular, examples: "Updated objects", "Updated job"`, }); - await spacesManager.updateSavedObjectsSpaces(objects, spacesToAdd, spacesToRemove); + await spacesManager.updateSavedObjectsSpaces(objectsToUpdate, spacesToAdd, spacesToRemove); const isSharedToAllSpaces = spacesToAdd.includes(ALL_SPACES_ID); let toastText: string; if (spacesToAdd.length > 0 && spacesToRemove.length > 0 && !isSharedToAllSpaces) { toastText = i18n.translate('xpack.spaces.shareToSpace.shareSuccessAddRemoveText', { - defaultMessage: `'{object}' was added to {spacesTargetAdd} and removed from {spacesTargetRemove}.`, // TODO: update to include # of references and/or # of tags + defaultMessage: `'{object}' {relativesCount, plural, =0 {was} =1 {and {relativesCount} related object were} other {and {relativesCount} related objects were}} added to {spacesTargetAdd} and removed from {spacesTargetRemove}.`, values: { object: title, + relativesCount, spacesTargetAdd: getSpacesTargetString(spacesToAdd), spacesTargetRemove: getSpacesTargetString(spacesToRemove), }, - description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget...' inputs. Example strings: "'Finance dashboard' was added to 1 space and removed from 2 spaces.", "'Finance dashboard' was added to 3 spaces and removed from all spaces."`, + description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget...' inputs. Example strings: "'Finance dashboard' was added to 1 space and removed from 2 spaces.", "'Finance dashboard' and 2 related objects were added to 3 spaces and removed from all spaces."`, }); } else if (spacesToAdd.length > 0) { toastText = i18n.translate('xpack.spaces.shareToSpace.shareSuccessAddText', { - defaultMessage: `'{object}' was added to {spacesTarget}.`, // TODO: update to include # of references and/or # of tags + defaultMessage: `'{object}' {relativesCount, plural, =0 {was} =1 {and {relativesCount} related object were} other {and {relativesCount} related objects were}} added to {spacesTarget}.`, values: { object: title, + relativesCount, spacesTarget: getSpacesTargetString(spacesToAdd), }, - description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget' input. Example strings: "'Finance dashboard' was added to 1 space.", "'Finance dashboard' was added to all spaces."`, + description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget' input. Example strings: "'Finance dashboard' was added to 1 space.", "'Finance dashboard' and 2 related objects were added to all spaces."`, }); } else { toastText = i18n.translate('xpack.spaces.shareToSpace.shareSuccessRemoveText', { - defaultMessage: `'{object}' was removed from {spacesTarget}.`, // TODO: update to include # of references and/or # of tags + defaultMessage: `'{object}' {relativesCount, plural, =0 {was} =1 {and {relativesCount} related object were} other {and {relativesCount} related objects were}} removed from {spacesTarget}.`, values: { object: title, + relativesCount, spacesTarget: getSpacesTargetString(spacesToRemove), }, - description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget' input. Example strings: "'Finance dashboard' was removed from 1 space.", "'Finance dashboard' was removed from all spaces."`, + description: `Uses output of xpack.spaces.shareToSpace.spacesTarget or xpack.spaces.shareToSpace.allSpacesTarget as 'spacesTarget' input. Example strings: "'Finance dashboard' was removed from 1 space.", "'Finance dashboard' and 2 related objects were removed from all spaces."`, }); } toastNotifications.addSuccess({ title: toastTitle, text: toastText }); @@ -131,7 +143,7 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { const { flyoutIcon, flyoutTitle = i18n.translate('xpack.spaces.shareToSpace.flyoutTitle', { - defaultMessage: 'Edit spaces for {objectNoun}', + defaultMessage: 'Assign {objectNoun} to spaces', values: { objectNoun: savedObjectTarget.noun }, }), enableCreateCopyCallout = false, @@ -154,13 +166,15 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { const [canShareToAllSpaces, setCanShareToAllSpaces] = useState(false); const [showMakeCopy, setShowMakeCopy] = useState(false); - const [{ isLoading, spaces }, setSpacesState] = useState<{ + const [{ isLoading, spaces, referenceGraph, aliasTargets }, setSpacesState] = useState<{ isLoading: boolean; spaces: ShareToSpaceTarget[]; - }>({ isLoading: true, spaces: [] }); + referenceGraph: SavedObjectReferenceWithContext[]; + aliasTargets: InternalLegacyUrlAliasTarget[]; + }>({ isLoading: true, spaces: [], referenceGraph: [], aliasTargets: [] }); useEffect(() => { const { type, id } = savedObjectTarget; - const getShareableReferences = spacesManager.getShareableReferences([{ type, id }]); // NOTE: not used yet, this is just included so you can see the request/response in Dev Tools + const getShareableReferences = spacesManager.getShareableReferences([{ type, id }]); const getPermissions = spacesManager.getShareSavedObjectPermissions(type); Promise.all([shareToSpacesDataPromise, getShareableReferences, getPermissions]) .then(([shareToSpacesData, shareableReferences, permissions]) => { @@ -176,6 +190,20 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { setSpacesState({ isLoading: false, spaces: [...shareToSpacesData.spacesMap].map(([, spaceTarget]) => spaceTarget), + referenceGraph: shareableReferences.objects, + aliasTargets: shareableReferences.objects.reduce( + (acc, x) => { + for (const space of x.spacesWithMatchingAliases ?? []) { + if (space !== '?') { + const spaceExists = shareToSpacesData.spacesMap.has(space); + // If the user does not have privileges to view all spaces, they will be redacted; we cannot attempt to disable aliases for redacted spaces. + acc.push({ targetSpace: space, targetType: x.type, sourceId: x.id, spaceExists }); + } + } + return acc; + }, + [] + ), }); }) .catch((e) => { @@ -195,7 +223,12 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { const getSelectionChanges = () => { if (!spaces.length) { - return { isSelectionChanged: false, spacesToAdd: [], spacesToRemove: [] }; + return { + isSelectionChanged: false, + spacesToAdd: [], + spacesToRemove: [], + aliasesToDisable: [], + }; } const activeSpaceId = !enableSpaceAgnosticBehavior && spaces.find((space) => space.isActiveSpace)!.id; @@ -231,21 +264,36 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { : isUnsharedFromAllSpaces ? [...activeSpaceArray, ...selectedSpacesToAdd] : selectedSpacesToAdd; + const spacesToAddSet = new Set(spacesToAdd); const spacesToRemove = isUnsharedFromAllSpaces || !isSharedToAllSpaces ? selectedSpacesToRemove : [...activeSpaceArray, ...initialSelection]; - return { isSelectionChanged, spacesToAdd, spacesToRemove }; + const aliasesToDisable = isSharedToAllSpaces + ? aliasTargets + : aliasTargets.filter(({ targetSpace }) => spacesToAddSet.has(targetSpace)); + return { isSelectionChanged, spacesToAdd, spacesToRemove, aliasesToDisable }; }; - const { isSelectionChanged, spacesToAdd, spacesToRemove } = getSelectionChanges(); + const { + isSelectionChanged, + spacesToAdd, + spacesToRemove, + aliasesToDisable, + } = getSelectionChanges(); + const [showAliasesToDisable, setShowAliasesToDisable] = useState(false); const [shareInProgress, setShareInProgress] = useState(false); async function startShare() { setShareInProgress(true); try { - await changeSpacesHandler(spacesToAdd, spacesToRemove); - onUpdate(); + if (aliasesToDisable.length) { + const aliases = aliasesToDisable.map(({ spaceExists, ...alias }) => alias); // only use 'targetSpace', 'targetType', and 'sourceId' fields + await spacesManager.disableLegacyUrlAliases(aliases); + } + await changeSpacesHandler(referenceGraph, spacesToAdd, spacesToRemove); + const updatedObjects = referenceGraph.map(({ type, id }) => ({ type, id })); // only use 'type' and 'id' fields + onUpdate(updatedObjects); onClose(); } catch (e) { setShareInProgress(false); @@ -264,27 +312,86 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { return ; } - // If the object has not been shared yet (e.g., it currently exists in exactly one space), and there is at least one space that we could - // share this object to, we want to display a callout to the user that explains the ramifications of shared objects. They might actually - // want to make a copy instead, so this callout contains a link that opens the Copy flyout. - const showCreateCopyCallout = - enableCreateCopyCallout && - spaces.length > 1 && - savedObjectTarget.namespaces.length === 1 && - !arraysAreEqual(savedObjectTarget.namespaces, [ALL_SPACES_ID]); - // Step 2: Share has not been initiated yet; User must fill out form to continue. + if (!showAliasesToDisable) { + // If the object has not been shared yet (e.g., it currently exists in exactly one space), and there is at least one space that we could + // share this object to, we want to display a callout to the user that explains the ramifications of shared objects. They might actually + // want to make a copy instead, so this callout contains a link that opens the Copy flyout. + const showCreateCopyCallout = + enableCreateCopyCallout && + spaces.length > 1 && + savedObjectTarget.namespaces.length === 1 && + !arraysAreEqual(savedObjectTarget.namespaces, [ALL_SPACES_ID]); + // Step 2: Share has not been initiated yet; User must fill out form to continue. + return ( + setShowMakeCopy(true)} + enableCreateNewSpaceLink={enableCreateNewSpaceLink} + enableSpaceAgnosticBehavior={enableSpaceAgnosticBehavior} + /> + ); + } + + return ; + }; + + const getFlyoutFooter = () => { + const filteredAliasesToDisable = aliasesToDisable.filter(({ spaceExists }) => spaceExists); + const showContinueButton = filteredAliasesToDisable.length && !showAliasesToDisable; return ( - setShowMakeCopy(true)} - enableCreateNewSpaceLink={enableCreateNewSpaceLink} - enableSpaceAgnosticBehavior={enableSpaceAgnosticBehavior} - /> + <> + + + + onClose()} + data-test-subj="sts-cancel-button" + disabled={shareInProgress} + > + + + + + {showContinueButton ? ( + setShowAliasesToDisable(true)} + data-test-subj="sts-continue-button" + disabled={isStartShareButtonDisabled} + > + + + ) : ( + startShare()} + data-test-subj="sts-save-button" + disabled={isStartShareButtonDisabled} + > + + + )} + + + ); }; @@ -317,54 +424,33 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { - - - {savedObjectTarget.icon && ( - - + + + + + {savedObjectTarget.icon && ( + + + + )} + + +

{savedObjectTarget.title}

+ - )} - - -

{savedObjectTarget.title}

-
-
- + + - + {getFlyoutBody()} - + - - - - onClose()} - data-test-subj="sts-cancel-button" - disabled={shareInProgress} - > - - - - - startShare()} - data-test-subj="sts-initiate-button" - disabled={isStartShareButtonDisabled} - > - - - - - + {getFlyoutFooter()} ); }; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx index 65ed0139d0dd8..7f8c659805c45 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_form.tsx @@ -61,7 +61,7 @@ export const ShareToSpaceForm = (props: Props) => { defaultMessage="Your changes appear in each space you select. {makeACopyLink} if you don't want to synchronize your changes." values={{ makeACopyLink: ( - makeCopy()}> + makeCopy()}> { ) : null; return ( -
+ <> {createCopyCallout} { enableCreateNewSpaceLink={enableCreateNewSpaceLink} enableSpaceAgnosticBehavior={enableSpaceAgnosticBehavior} /> -
+ ); }; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/types.ts b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/types.ts new file mode 100644 index 0000000000000..ba39dd2499f4c --- /dev/null +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LegacyUrlAliasTarget } from '../../../common'; + +export interface InternalLegacyUrlAliasTarget extends LegacyUrlAliasTarget { + /** + * We could potentially have an alias for a space that does not exist; in that case, we may need disable it, but we don't want to show it + * in the UI. + */ + spaceExists: boolean; +} diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/share_saved_objects_to_space_action.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/share_saved_objects_to_space_action.tsx index 9a0d171342a80..90dda8ad0b013 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/share_saved_objects_to_space_action.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/share_saved_objects_to_space_action.tsx @@ -44,13 +44,13 @@ export class ShareToSpaceSavedObjectsManagementAction extends SavedObjectsManage return namespaceType === 'multiple' && !hiddenType && hasCapability; }, onClick: (object: SavedObjectsManagementRecord) => { - this.isDataChanged = false; + this.objectsToRefresh = []; this.start(object); }, }; - public refreshOnFinish = () => this.isDataChanged; + public refreshOnFinish = () => this.objectsToRefresh; - private isDataChanged: boolean = false; + private objectsToRefresh: Array<{ type: string; id: string }> = []; constructor(private readonly spacesApiUi: SpacesApiUi) { super(); @@ -70,7 +70,8 @@ export class ShareToSpaceSavedObjectsManagementAction extends SavedObjectsManage icon: this.record.meta.icon, }, flyoutIcon: 'share', - onUpdate: () => (this.isDataChanged = true), + onUpdate: (updatedObjects: Array<{ type: string; id: string }>) => + (this.objectsToRefresh = [...updatedObjects]), onClose: this.onClose, enableCreateCopyCallout: true, enableCreateNewSpaceLink: true, diff --git a/x-pack/plugins/spaces/public/space_avatar/space_avatar_internal.tsx b/x-pack/plugins/spaces/public/space_avatar/space_avatar_internal.tsx index 9a3a112110bbf..91b4dbf8a964e 100644 --- a/x-pack/plugins/spaces/public/space_avatar/space_avatar_internal.tsx +++ b/x-pack/plugins/spaces/public/space_avatar/space_avatar_internal.tsx @@ -20,6 +20,12 @@ interface Props { size?: 's' | 'm' | 'l' | 'xl'; className?: string; announceSpaceName?: boolean; + /** + * This property is passed to the underlying `EuiAvatar` component. If enabled, the SpaceAvatar will have a grayed out appearance. For + * example, this can be useful when rendering a list of spaces for a specific feature, if the feature is disabled in one of those spaces. + * Default: false. + */ + isDisabled?: boolean; } export const SpaceAvatarInternal: FC = (props: Props) => { diff --git a/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx b/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx index 1a512fb2d31f4..ac7e6446f2ccd 100644 --- a/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx +++ b/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx @@ -142,11 +142,10 @@ export const SpaceListInternal = ({ }> {displayedSpaces.map((space) => { - // color may be undefined, which is intentional; SpacesAvatar calls the getSpaceColor function before rendering - const color = space.isFeatureDisabled ? 'hollow' : space.color; + const isDisabled = space.isFeatureDisabled; return ( - + ); })} diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts index 39c06a2bc874d..5282163f93b15 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts @@ -21,6 +21,7 @@ function createSpacesManagerMock() { createSpace: jest.fn().mockResolvedValue(undefined), updateSpace: jest.fn().mockResolvedValue(undefined), deleteSpace: jest.fn().mockResolvedValue(undefined), + disableLegacyUrlAliases: jest.fn().mockResolvedValue(undefined), copySavedObjects: jest.fn().mockResolvedValue(undefined), getShareableReferences: jest.fn().mockResolvedValue(undefined), updateSavedObjectsSpaces: jest.fn().mockResolvedValue(undefined), diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts index ff18b2965e8b9..d09177a915d99 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts @@ -154,4 +154,33 @@ describe('SpacesManager', () => { ); }); }); + + describe('#getShareableReferences', () => { + it('retrieves the shareable references, filters out references that are tags, and returns the result', async () => { + const obj1 = { type: 'not-a-tag', id: '1' }; // requested object + const obj2 = { type: 'tag', id: '2' }; // requested object + const obj3 = { type: 'tag', id: '3' }; // referenced object + const obj4 = { type: 'not-a-tag', id: '4' }; // referenced object + + const coreStart = coreMock.createStart(); + coreStart.http.post.mockResolvedValue({ objects: [obj1, obj2, obj3, obj4] }); // A realistic response would include additional fields besides 'type' and 'id', but they are not needed for this test case + const spacesManager = new SpacesManager(coreStart.http); + + const requestObjects = [obj1, obj2]; + const result = await spacesManager.getShareableReferences(requestObjects); + expect(coreStart.http.post).toHaveBeenCalledTimes(1); + expect(coreStart.http.post).toHaveBeenLastCalledWith( + '/api/spaces/_get_shareable_references', + { body: JSON.stringify({ objects: requestObjects }) } + ); + expect(result).toEqual({ + objects: [ + obj1, // obj1 is not a tag + obj2, // obj2 is a tag, but it was included in the request, so it is not excluded from the response + // obj3 is a tag, but it was not included in the request, so it is excluded from the response + obj4, // obj4 is not a tag + ], + }); + }); + }); }); diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts index a7201def5ed40..845373bf22299 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -15,7 +15,7 @@ import type { } from 'src/core/public'; import type { Space } from 'src/plugins/spaces_oss/common'; -import type { GetAllSpacesOptions, GetSpaceResult } from '../../common'; +import type { GetAllSpacesOptions, GetSpaceResult, LegacyUrlAliasTarget } from '../../common'; import type { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; interface SavedObjectTarget { @@ -23,6 +23,8 @@ interface SavedObjectTarget { id: string; } +const TAG_TYPE = 'tag'; + export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); @@ -90,6 +92,12 @@ export class SpacesManager { await this.http.delete(`/api/spaces/space/${encodeURIComponent(space.id)}`); } + public async disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]) { + await this.http.post('/api/spaces/_disable_legacy_url_aliases', { + body: JSON.stringify({ aliases }), + }); + } + public async copySavedObjects( objects: SavedObjectTarget[], spaces: string[], @@ -142,9 +150,21 @@ export class SpacesManager { public async getShareableReferences( objects: SavedObjectTarget[] ): Promise { - return this.http.post(`/api/spaces/_get_shareable_references`, { - body: JSON.stringify({ objects }), - }); + const response = await this.http.post( + `/api/spaces/_get_shareable_references`, + { body: JSON.stringify({ objects }) } + ); + + // We should exclude any child-reference tags because we don't yet support reconciling/merging duplicate tags. In other words: tags can + // be shared directly, but if a tag is only included as a reference of a requested object, it should not be shared. + const requestedObjectsSet = objects.reduce( + (acc, { type, id }) => acc.add(`${type}:${id}`), + new Set() + ); + const filteredObjects = response.objects.filter( + ({ type, id }) => type !== TAG_TYPE || requestedObjectsSet.has(`${type}:${id}`) + ); + return { objects: filteredObjects }; } public async updateSavedObjectsSpaces( diff --git a/x-pack/plugins/spaces/public/suspense_error_boundary/suspense_error_boundary.tsx b/x-pack/plugins/spaces/public/suspense_error_boundary/suspense_error_boundary.tsx index e90920f3f1e25..e2a1ce69deee8 100644 --- a/x-pack/plugins/spaces/public/suspense_error_boundary/suspense_error_boundary.tsx +++ b/x-pack/plugins/spaces/public/suspense_error_boundary/suspense_error_boundary.tsx @@ -14,6 +14,12 @@ import type { NotificationsStart } from 'src/core/public'; interface Props { notifications: NotificationsStart; + /** + * Whether or not to show a loading spinner while waiting for the child components to load. + * + * Default is true. + */ + showLoadingSpinner?: boolean; } interface State { @@ -44,11 +50,12 @@ export class SuspenseErrorBoundary extends Component, S } render() { - const { children, notifications } = this.props; + const { children, notifications, showLoadingSpinner = true } = this.props; const { error } = this.state; if (!notifications || error) { return null; } - return }>{children}; + const fallback = showLoadingSpinner ? : null; + return {children}; } } diff --git a/x-pack/plugins/spaces/public/ui_api/components.tsx b/x-pack/plugins/spaces/public/ui_api/components.tsx index b564e96be4c41..a277e3a1dd119 100644 --- a/x-pack/plugins/spaces/public/ui_api/components.tsx +++ b/x-pack/plugins/spaces/public/ui_api/components.tsx @@ -34,9 +34,15 @@ export const getComponents = ({ /** * Returns a function that creates a lazy-loading version of a component. */ - function wrapLazy(fn: () => Promise>) { + function wrapLazy(fn: () => Promise>, options: { showLoadingSpinner?: boolean } = {}) { + const { showLoadingSpinner } = options; return (props: JSX.IntrinsicAttributes & PropsWithRef>) => ( - + ); } @@ -44,7 +50,7 @@ export const getComponents = ({ getSpacesContextProvider: wrapLazy(() => getSpacesContextProviderWrapper({ spacesManager, getStartServices }) ), - getShareToSpaceFlyout: wrapLazy(getShareToSpaceFlyoutComponent), + getShareToSpaceFlyout: wrapLazy(getShareToSpaceFlyoutComponent, { showLoadingSpinner: false }), getSpaceList: wrapLazy(getSpaceListComponent), getLegacyUrlConflict: wrapLazy(() => getLegacyUrlConflict({ getStartServices })), getSpaceAvatar: wrapLazy(getSpaceAvatarComponent), diff --git a/x-pack/plugins/spaces/public/ui_api/lazy_wrapper.tsx b/x-pack/plugins/spaces/public/ui_api/lazy_wrapper.tsx index 9c3336cfca01d..384eee6c439c9 100644 --- a/x-pack/plugins/spaces/public/ui_api/lazy_wrapper.tsx +++ b/x-pack/plugins/spaces/public/ui_api/lazy_wrapper.tsx @@ -17,12 +17,14 @@ import { SuspenseErrorBoundary } from '../suspense_error_boundary'; interface InternalProps { fn: () => Promise>; getStartServices: StartServicesAccessor; + showLoadingSpinner?: boolean; props: JSX.IntrinsicAttributes & PropsWithRef>; } export const LazyWrapper: (props: InternalProps) => ReactElement | null = ({ fn, getStartServices, + showLoadingSpinner, props, }) => { const { value: startServices = [{ notifications: undefined }] } = useAsync(getStartServices); @@ -35,7 +37,7 @@ export const LazyWrapper: (props: InternalProps) => ReactElement | null = } return ( - + ); diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts index c779e92a3ae9b..4765b06f5a02a 100644 --- a/x-pack/plugins/spaces/server/index.ts +++ b/x-pack/plugins/spaces/server/index.ts @@ -23,7 +23,12 @@ export { SpacesPluginSetup, SpacesPluginStart } from './plugin'; export { SpacesServiceSetup, SpacesServiceStart } from './spaces_service'; export { ISpacesClient, SpacesClientRepositoryFactory, SpacesClientWrapper } from './spaces_client'; -export { GetAllSpacesOptions, GetAllSpacesPurpose, GetSpaceResult } from '../common'; +export { + GetAllSpacesOptions, + GetAllSpacesPurpose, + GetSpaceResult, + LegacyUrlAliasTarget, +} from '../common'; // re-export types from oss definition export type { Space } from 'src/plugins/spaces_oss/common'; diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts index 13398925490fa..acc13be6802c2 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -35,6 +35,7 @@ export const createMockSavedObjectsRepository = (spaces: any[] = []) => { } return {}; }), + bulkUpdate: jest.fn(), delete: jest.fn((type: string, id: string) => { return {}; }), diff --git a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts new file mode 100644 index 0000000000000..e475b6b21569a --- /dev/null +++ b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as Rx from 'rxjs'; + +import type { RouteValidatorConfig } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { + coreMock, + httpServerMock, + httpServiceMock, + loggingSystemMock, +} from 'src/core/server/mocks'; + +import { spacesConfig } from '../../../lib/__fixtures__'; +import { SpacesClientService } from '../../../spaces_client'; +import { SpacesService } from '../../../spaces_service'; +import { usageStatsClientMock } from '../../../usage_stats/usage_stats_client.mock'; +import { usageStatsServiceMock } from '../../../usage_stats/usage_stats_service.mock'; +import { + createMockSavedObjectsRepository, + createSpaces, + mockRouteContext, + mockRouteContextWithInvalidLicense, +} from '../__fixtures__'; +import { initDisableLegacyUrlAliasesApi } from './disable_legacy_url_aliases'; + +describe('_disable_legacy_url_aliases', () => { + const spacesSavedObjects = createSpaces(); + + const setup = async () => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter(); + + const coreStart = coreMock.createStart(); + + const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects); + + const log = loggingSystemMock.create().get('spaces'); + + const clientService = new SpacesClientService(jest.fn()); + clientService + .setup({ config$: Rx.of(spacesConfig) }) + .setClientRepositoryFactory(() => savedObjectsRepositoryMock); + + const service = new SpacesService(); + service.setup({ + basePath: httpService.basePath, + }); + + const usageStatsClient = usageStatsClientMock.create(); + const usageStatsServicePromise = Promise.resolve( + usageStatsServiceMock.createSetupContract(usageStatsClient) + ); + + const clientServiceStart = clientService.start(coreStart); + + const spacesServiceStart = service.start({ + basePath: coreStart.http.basePath, + spacesClientService: clientServiceStart, + }); + + initDisableLegacyUrlAliasesApi({ + externalRouter: router, + getStartServices: async () => [coreStart, {}, {}], + log, + getSpacesService: () => spacesServiceStart, + usageStatsServicePromise, + }); + + const [routeDefinition, routeHandler] = router.post.mock.calls[0]; + + return { + routeValidation: routeDefinition.validate as RouteValidatorConfig<{}, {}, {}>, + routeHandler, + savedObjectsRepositoryMock, + usageStatsClient, + }; + }; + + it('records usageStats data', async () => { + const payload = { + aliases: [{ targetSpace: 'space-1', targetType: 'type-1', sourceId: 'id-1' }], + }; + + const { routeHandler, usageStatsClient } = await setup(); + + const request = httpServerMock.createKibanaRequest({ + body: payload, + method: 'post', + }); + + await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(usageStatsClient.incrementDisableLegacyUrlAliases).toHaveBeenCalled(); + }); + + it('should disable the provided aliases', async () => { + const payload = { + aliases: [{ targetSpace: 'space-1', targetType: 'type-1', sourceId: 'id-1' }], + }; + + const { routeHandler, savedObjectsRepositoryMock } = await setup(); + + const request = httpServerMock.createKibanaRequest({ + body: payload, + method: 'post', + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + const { status } = response; + + expect(status).toEqual(204); + expect(savedObjectsRepositoryMock.bulkUpdate).toHaveBeenCalledTimes(1); + expect(savedObjectsRepositoryMock.bulkUpdate).toHaveBeenCalledWith([ + { type: 'legacy-url-alias', id: 'space-1:type-1:id-1', attributes: { disabled: true } }, + ]); + }); + + it(`returns http/403 when the license is invalid`, async () => { + const { routeHandler } = await setup(); + + const request = httpServerMock.createKibanaRequest({ + method: 'post', + }); + + const response = await routeHandler( + mockRouteContextWithInvalidLicense, + request, + kibanaResponseFactory + ); + + expect(response.status).toEqual(403); + expect(response.payload).toEqual({ + message: 'License is invalid for spaces', + }); + }); +}); diff --git a/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts new file mode 100644 index 0000000000000..a14744ddc5eeb --- /dev/null +++ b/x-pack/plugins/spaces/server/routes/api/external/disable_legacy_url_aliases.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { wrapError } from '../../../lib/errors'; +import { createLicensedRouteHandler } from '../../lib'; +import type { ExternalRouteDeps } from './'; + +export function initDisableLegacyUrlAliasesApi(deps: ExternalRouteDeps) { + const { externalRouter, getSpacesService, usageStatsServicePromise } = deps; + const usageStatsClientPromise = usageStatsServicePromise.then(({ getClient }) => getClient()); + + externalRouter.post( + { + path: '/api/spaces/_disable_legacy_url_aliases', + validate: { + body: schema.object({ + aliases: schema.arrayOf( + schema.object({ + targetSpace: schema.string(), + targetType: schema.string(), + sourceId: schema.string(), + }) + ), + }), + }, + }, + createLicensedRouteHandler(async (_context, request, response) => { + const spacesClient = getSpacesService().createSpacesClient(request); + + const { aliases } = request.body; + + usageStatsClientPromise.then((usageStatsClient) => + usageStatsClient.incrementDisableLegacyUrlAliases() + ); + + try { + await spacesClient.disableLegacyUrlAliases(aliases); + return response.noContent(); + } catch (error) { + return response.customError(wrapError(error)); + } + }) + ); +} diff --git a/x-pack/plugins/spaces/server/routes/api/external/index.ts b/x-pack/plugins/spaces/server/routes/api/external/index.ts index 9cebd8d0f9352..c42e57dea736d 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/index.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/index.ts @@ -12,6 +12,7 @@ import type { SpacesRouter } from '../../../types'; import type { UsageStatsServiceSetup } from '../../../usage_stats'; import { initCopyToSpacesApi } from './copy_to_space'; import { initDeleteSpacesApi } from './delete'; +import { initDisableLegacyUrlAliasesApi } from './disable_legacy_url_aliases'; import { initGetSpaceApi } from './get'; import { initGetAllSpacesApi } from './get_all'; import { initGetShareableReferencesApi } from './get_shareable_references'; @@ -36,4 +37,5 @@ export function initExternalSpacesApi(deps: ExternalRouteDeps) { initCopyToSpacesApi(deps); initUpdateObjectsSpacesApi(deps); initGetShareableReferencesApi(deps); + initDisableLegacyUrlAliasesApi(deps); } diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts index f03e26ea8e714..d893d2b089f89 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts @@ -30,6 +30,7 @@ const createSpacesClientMock = () => create: jest.fn().mockImplementation((space: Space) => Promise.resolve(space)), update: jest.fn().mockImplementation((space: Space) => Promise.resolve(space)), delete: jest.fn(), + disableLegacyUrlAliases: jest.fn(), } as unknown) as jest.Mocked); export const spacesClientMock = { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index 1d5d3851ce9c5..ae9fc254c0934 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -341,4 +341,25 @@ describe('#delete', () => { expect(mockCallWithRequestRepository.delete).toHaveBeenCalledWith('space', id); expect(mockCallWithRequestRepository.deleteByNamespace).toHaveBeenCalledWith(id); }); + + describe('#disableLegacyUrlAliases', () => { + test(`updates legacy URL aliases using callWithRequestRepository`, async () => { + const mockDebugLogger = createMockDebugLogger(); + const mockConfig = createMockConfig(); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + + const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository); + const aliases = [ + { targetSpace: 'space1', targetType: 'foo', sourceId: '123' }, + { targetSpace: 'space2', targetType: 'bar', sourceId: '456' }, + ]; + await client.disableLegacyUrlAliases(aliases); + + expect(mockCallWithRequestRepository.bulkUpdate).toHaveBeenCalledTimes(1); + expect(mockCallWithRequestRepository.bulkUpdate).toHaveBeenCalledWith([ + { type: 'legacy-url-alias', id: 'space1:foo:123', attributes: { disabled: true } }, + { type: 'legacy-url-alias', id: 'space2:bar:456', attributes: { disabled: true } }, + ]); + }); + }); }); diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index 02aa4d0b976c0..824d6e28b9923 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -11,7 +11,12 @@ import { omit } from 'lodash'; import type { ISavedObjectsRepository, SavedObject } from 'src/core/server'; import type { Space } from 'src/plugins/spaces_oss/common'; -import type { GetAllSpacesOptions, GetAllSpacesPurpose, GetSpaceResult } from '../../common'; +import type { + GetAllSpacesOptions, + GetAllSpacesPurpose, + GetSpaceResult, + LegacyUrlAliasTarget, +} from '../../common'; import { isReservedSpace } from '../../common'; import type { ConfigType } from '../config'; @@ -22,6 +27,7 @@ const SUPPORTED_GET_SPACE_PURPOSES: GetAllSpacesPurpose[] = [ 'shareSavedObjectsIntoSpace', ]; const DEFAULT_PURPOSE = 'any'; +const LEGACY_URL_ALIAS_TYPE = 'legacy-url-alias'; /** * Client interface for interacting with spaces. @@ -57,6 +63,12 @@ export interface ISpacesClient { * @param id the id of the space to delete. */ delete(id: string): Promise; + + /** + * Disables the specified legacy URL aliases. + * @param aliases the aliases to disable. + */ + disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]): Promise; } /** @@ -135,6 +147,15 @@ export class SpacesClient implements ISpacesClient { await this.repository.delete('space', id); } + public async disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]) { + const attributes = { disabled: true }; + const objectsToUpdate = aliases.map(({ targetSpace, targetType, sourceId }) => { + const id = `${targetSpace}:${targetType}:${sourceId}`; + return { type: LEGACY_URL_ALIAS_TYPE, id, attributes }; + }); + await this.repository.bulkUpdate(objectsToUpdate); + } + private transformSavedObjectToSpace(savedObject: SavedObject) { return { id: savedObject.id, diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts index 19228614dc614..655463ed64a30 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts @@ -40,6 +40,7 @@ const MOCK_USAGE_STATS: UsageStats = { 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.no': 0, 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes': 6, 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no': 7, + 'apiCalls.disableLegacyUrlAliases.total': 17, }; function setup({ diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index 93892378717d5..b5c0972031a8f 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -425,6 +425,12 @@ export function getSpacesUsageCollector( 'The number of times the "Resolve Copy Saved Objects Errors" API has been called with "createNewCopies" set to false.', }, }, + 'apiCalls.disableLegacyUrlAliases.total': { + type: 'long', + _meta: { + description: 'The number of times the "Disable Legacy URL Aliases" API has been called.', + }, + }, }, fetch: async ({ esClient }: CollectorFetchContext) => { const { licensing, kibanaIndexConfig$, features, usageStatsServicePromise } = deps; diff --git a/x-pack/plugins/spaces/server/usage_stats/types.ts b/x-pack/plugins/spaces/server/usage_stats/types.ts index c0ea2c98ac4b8..81fe720cba745 100644 --- a/x-pack/plugins/spaces/server/usage_stats/types.ts +++ b/x-pack/plugins/spaces/server/usage_stats/types.ts @@ -18,4 +18,5 @@ export interface UsageStats { 'apiCalls.resolveCopySavedObjectsErrors.kibanaRequest.no'?: number; 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.yes'?: number; 'apiCalls.resolveCopySavedObjectsErrors.createNewCopiesEnabled.no'?: number; + 'apiCalls.disableLegacyUrlAliases.total'?: number; } diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts index 32380b1a21048..c9f73451c7553 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts @@ -12,6 +12,7 @@ const createUsageStatsClientMock = () => getUsageStats: jest.fn().mockResolvedValue({}), incrementCopySavedObjects: jest.fn().mockResolvedValue(null), incrementResolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(null), + incrementDisableLegacyUrlAliases: jest.fn().mockResolvedValue(null), } as unknown) as jest.Mocked); export const usageStatsClientMock = { diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts index 6a56cb68d2a16..c5b63a4d007b9 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts @@ -14,6 +14,7 @@ import type { } from './usage_stats_client'; import { COPY_STATS_PREFIX, + DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX, RESOLVE_COPY_STATS_PREFIX, UsageStatsClient, } from './usage_stats_client'; @@ -50,6 +51,7 @@ describe('UsageStatsClient', () => { `${RESOLVE_COPY_STATS_PREFIX}.kibanaRequest.no`, `${RESOLVE_COPY_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${RESOLVE_COPY_STATS_PREFIX}.createNewCopiesEnabled.no`, + `${DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX}.total`, ], { initialize: true } ); @@ -131,7 +133,7 @@ describe('UsageStatsClient', () => { }); describe('#incrementResolveCopySavedObjectsErrors', () => { - it('does not throw an error if repository create operation fails', async () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); @@ -182,4 +184,27 @@ describe('UsageStatsClient', () => { ); }); }); + + describe('#incrementDisableLegacyUrlAliases', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + await expect(usageStatsClient.incrementDisableLegacyUrlAliases()).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + }); + + it('uses the appropriate counter fields', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + await usageStatsClient.incrementDisableLegacyUrlAliases(); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + SPACES_USAGE_STATS_TYPE, + SPACES_USAGE_STATS_ID, + [`${DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX}.total`], + incrementOptions + ); + }); + }); }); diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts index 5093dd42b5bfa..5dcf106d6cfb4 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts @@ -21,6 +21,7 @@ export type IncrementResolveCopySavedObjectsErrorsOptions = BaseIncrementOptions export const COPY_STATS_PREFIX = 'apiCalls.copySavedObjects'; export const RESOLVE_COPY_STATS_PREFIX = 'apiCalls.resolveCopySavedObjectsErrors'; +export const DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX = 'apiCalls.disableLegacyUrlAliases'; const ALL_COUNTER_FIELDS = [ `${COPY_STATS_PREFIX}.total`, `${COPY_STATS_PREFIX}.kibanaRequest.yes`, @@ -34,6 +35,7 @@ const ALL_COUNTER_FIELDS = [ `${RESOLVE_COPY_STATS_PREFIX}.kibanaRequest.no`, `${RESOLVE_COPY_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${RESOLVE_COPY_STATS_PREFIX}.createNewCopiesEnabled.no`, + `${DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX}.total`, ]; export class UsageStatsClient { constructor( @@ -87,6 +89,11 @@ export class UsageStatsClient { await this.updateUsageStats(counterFieldNames, RESOLVE_COPY_STATS_PREFIX); } + public async incrementDisableLegacyUrlAliases() { + const counterFieldNames = ['total']; + await this.updateUsageStats(counterFieldNames, DISABLE_LEGACY_URL_ALIASES_STATS_PREFIX); + } + private async updateUsageStats(counterFieldNames: string[], prefix: string) { const options = { refresh: false }; try { 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 39852ebaeb46b..bab4244139df0 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5763,6 +5763,12 @@ "_meta": { "description": "The number of times the \"Resolve Copy Saved Objects Errors\" API has been called with \"createNewCopies\" set to false." } + }, + "apiCalls.disableLegacyUrlAliases.total": { + "type": "long", + "_meta": { + "description": "The number of times the \"Disable Legacy URL Aliases\" API has been called." + } } } }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1b44200566405..cd2fafb0abbc0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3412,7 +3412,6 @@ "savedObjectsManagement.objects.savedObjectsDescription": "保存された検索、ビジュアライゼーション、ダッシュボードのインポート、エクスポート、管理を行います。", "savedObjectsManagement.objects.savedObjectsTitle": "保存されたオブジェクト", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "キャンセル", - "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "削除", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName": "Id", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName": "タイトル", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName": "型", @@ -22106,7 +22105,6 @@ "xpack.spaces.shareToSpace.columnTitle": "共有されているスペース", "xpack.spaces.shareToSpace.currentSpaceBadge": "現在", "xpack.spaces.shareToSpace.featureIsDisabledTooltip": "この機能はこのスペースでは無効です。", - "xpack.spaces.shareToSpace.flyoutTitle": "{objectNoun}のスペースを編集", "xpack.spaces.shareToSpace.legacyUrlConflictBody": "現在、{objectNoun} [id={currentObjectId}]を表示しています。このページのレガシーURLは別の{objectNoun} [id={otherObjectId}]を示しています。", "xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "閉じる", "xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "他の{objectNoun}に移動", @@ -22125,14 +22123,7 @@ "xpack.spaces.shareToSpace.shareModeControl.hiddenCountLabel": "+{hiddenCount}個が非表示", "xpack.spaces.shareToSpace.shareModeControl.selectedCountLabel": "{selectedCount}個が選択済み", "xpack.spaces.shareToSpace.shareModeControl.selectSpacesLabel": "スペースを選択", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotCheckTooltip": "このオプションを使用するには、追加権限が必要です。", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotUncheckTooltip": "このオプションを変更するには、追加権限が必要です。", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.text": "現在と将来のすべてのスペースで{objectNoun}を使用可能にします。", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.title": "すべてのスペース", - "xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.text": "選択したスペースでのみ{objectNoun}を使用可能にします。", - "xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.title": "スペースを選択", "xpack.spaces.shareToSpace.shareSuccessTitle": "{objectNoun}を更新しました", - "xpack.spaces.shareToSpace.shareToSpacesButton": "保存して閉じる", "xpack.spaces.shareToSpace.shareWarningBody": "変更は選択した各スペースに表示されます。変更を同期しない場合は、{makeACopyLink}。", "xpack.spaces.shareToSpace.shareWarningLink": "コピーを作成", "xpack.spaces.shareToSpace.shareWarningTitle": "変更はスペース全体で同期されます", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9636aa9ba5282..2522abd40f07f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3436,7 +3436,6 @@ "savedObjectsManagement.objects.savedObjectsDescription": "导入、导出和管理您的已保存搜索、可视化和仪表板。", "savedObjectsManagement.objects.savedObjectsTitle": "已保存对象", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "取消", - "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "删除", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName": "ID", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName": "标题", "savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName": "类型", @@ -22460,7 +22459,6 @@ "xpack.spaces.shareToSpace.columnTitle": "共享工作区", "xpack.spaces.shareToSpace.currentSpaceBadge": "当前", "xpack.spaces.shareToSpace.featureIsDisabledTooltip": "此功能在此工作区中已禁用。", - "xpack.spaces.shareToSpace.flyoutTitle": "编辑 {objectNoun} 的工作区", "xpack.spaces.shareToSpace.legacyUrlConflictBody": "当前您正在查看 {objectNoun} [id={currentObjectId}]。此页面的旧 URL 显示不同的 {objectNoun} [id={otherObjectId}]。", "xpack.spaces.shareToSpace.legacyUrlConflictDismissButton": "关闭", "xpack.spaces.shareToSpace.legacyUrlConflictLinkButton": "前往其他 {objectNoun}", @@ -22479,14 +22477,9 @@ "xpack.spaces.shareToSpace.shareModeControl.hiddenCountLabel": "+{hiddenCount} 个已隐藏", "xpack.spaces.shareToSpace.shareModeControl.selectedCountLabel": "{selectedCount} 个已选择", "xpack.spaces.shareToSpace.shareModeControl.selectSpacesLabel": "选择工作区", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotCheckTooltip": "您还需要其他权限,才能使用此选项。", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.cannotUncheckTooltip": "您还需要其他权限,才能更改此选项。", "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.text": "使 {objectNoun} 在当前和将来的所有工作区中都可用。", - "xpack.spaces.shareToSpace.shareModeControl.shareToAllSpaces.title": "所有工作区", "xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.text": "仅使 {objectNoun} 在选定工作区中可用。", - "xpack.spaces.shareToSpace.shareModeControl.shareToExplicitSpaces.title": "选择工作区", "xpack.spaces.shareToSpace.shareSuccessTitle": "已更新 {objectNoun}", - "xpack.spaces.shareToSpace.shareToSpacesButton": "保存并关闭", "xpack.spaces.shareToSpace.shareWarningBody": "您的更改显示在您选择的每个工作区中。如果不想同步您的更改,{makeACopyLink}。", "xpack.spaces.shareToSpace.shareWarningLink": "创建副本", "xpack.spaces.shareToSpace.shareWarningTitle": "更改已在工作区之间同步", diff --git a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts new file mode 100644 index 0000000000000..fdf827a931054 --- /dev/null +++ b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { SuperTest } from 'supertest'; +import type { KibanaClient } from '@elastic/elasticsearch/api/kibana'; +import { LegacyUrlAlias } from 'src/core/server/saved_objects/object_types'; +import { SPACES } from '../lib/spaces'; +import { getUrlPrefix } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; +import { + ExpectResponseBody, + TestDefinition, + TestSuite, +} from '../../../saved_object_api_integration/common/lib/types'; + +export interface DisableLegacyUrlAliasesTestDefinition extends TestDefinition { + request: { + aliases: Array<{ targetSpace: string; targetType: string; sourceId: string }>; + }; +} +export type DisableLegacyUrlAliasesTestSuite = TestSuite; +export interface DisableLegacyUrlAliasesTestCase { + targetSpace: string; + targetType: string; + sourceId: string; +} + +const LEGACY_URL_ALIAS_TYPE = 'legacy-url-alias'; +interface RawLegacyUrlAlias { + [LEGACY_URL_ALIAS_TYPE]: LegacyUrlAlias; +} + +export const TEST_CASE_TARGET_TYPE = 'sharedtype'; +export const TEST_CASE_SOURCE_ID = 'default_only'; // two aliases exist for default_only: one in space_1, and one in space_2 +const createRequest = (alias: DisableLegacyUrlAliasesTestCase) => ({ + aliases: [alias], +}); +const getTestTitle = ({ targetSpace, targetType, sourceId }: DisableLegacyUrlAliasesTestCase) => { + return `for alias '${targetSpace}:${targetType}:${sourceId}'`; +}; + +export function disableLegacyUrlAliasesTestSuiteFactory( + es: KibanaClient, + esArchiver: any, + supertest: SuperTest +) { + const expectResponseBody = ( + testCase: DisableLegacyUrlAliasesTestCase, + statusCode: 204 | 403 + ): ExpectResponseBody => async (response: Record) => { + if (statusCode === 403) { + expect(response.body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `Unable to disable aliases for ${testCase.targetType}`, + }); + } + const { targetSpace, targetType, sourceId } = testCase; + const esResponse = await es.get({ + index: '.kibana', + id: `${LEGACY_URL_ALIAS_TYPE}:${targetSpace}:${targetType}:${sourceId}`, + }); + const doc = esResponse.body._source!; + expect(doc).not.to.be(undefined); + expect(doc[LEGACY_URL_ALIAS_TYPE].disabled).to.be(statusCode === 204 ? true : undefined); + }; + const createTestDefinitions = ( + testCases: DisableLegacyUrlAliasesTestCase | DisableLegacyUrlAliasesTestCase[], + forbidden: boolean, + options: { + responseBodyOverride?: ExpectResponseBody; + } = {} + ): DisableLegacyUrlAliasesTestDefinition[] => { + const cases = Array.isArray(testCases) ? testCases : [testCases]; + const responseStatusCode = forbidden ? 403 : 204; + return cases.map((x) => ({ + title: getTestTitle(x), + responseStatusCode, + request: createRequest(x), + responseBody: options?.responseBodyOverride || expectResponseBody(x, responseStatusCode), + })); + }; + + const makeDisableLegacyUrlAliasesTest = (describeFn: Mocha.SuiteFunction) => ( + description: string, + definition: DisableLegacyUrlAliasesTestSuite + ) => { + const { user, spaceId = SPACES.DEFAULT.spaceId, tests } = definition; + + describeFn(description, () => { + before(() => + esArchiver.load( + 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' + ) + ); + after(() => + esArchiver.unload( + 'x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces' + ) + ); + + for (const test of tests) { + it(`should return ${test.responseStatusCode} ${test.title}`, async () => { + const requestBody = test.request; + await supertest + .post(`${getUrlPrefix(spaceId)}/api/spaces/_disable_legacy_url_aliases`) + .auth(user?.username, user?.password) + .send(requestBody) + .expect(test.responseStatusCode) + .then(test.responseBody); + }); + } + }); + }; + + const addTests = makeDisableLegacyUrlAliasesTest(describe); + // @ts-ignore + addTests.only = makeDisableLegacyUrlAliasesTest(describe.only); + + return { + addTests, + createTestDefinitions, + }; +} diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts new file mode 100644 index 0000000000000..c89bc519468ad --- /dev/null +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/disable_legacy_url_aliases.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SPACES } from '../../common/lib/spaces'; +import { getTestScenarios } from '../../../saved_object_api_integration/common/lib/saved_object_test_utils'; +import { TestUser } from '../../../saved_object_api_integration/common/lib/types'; +import { + disableLegacyUrlAliasesTestSuiteFactory, + DisableLegacyUrlAliasesTestCase, + TEST_CASE_TARGET_TYPE, + TEST_CASE_SOURCE_ID, + DisableLegacyUrlAliasesTestDefinition, +} from '../../common/suites/disable_legacy_url_aliases'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +const { + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (...spaceIds: string[]): DisableLegacyUrlAliasesTestCase[] => { + return spaceIds.map((targetSpace) => ({ + targetSpace, + targetType: TEST_CASE_TARGET_TYPE, + sourceId: TEST_CASE_SOURCE_ID, + })); +}; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + const { addTests, createTestDefinitions } = disableLegacyUrlAliasesTestSuiteFactory( + es, + esArchiver, + supertest + ); + + describe('_disable_legacy_url_aliases', () => { + const _addTests = (user: TestUser, tests: DisableLegacyUrlAliasesTestDefinition[]) => { + addTests(`${user.description}`, { user, tests }); + }; + getTestScenarios().security.forEach(({ users }) => { + // We are intentionally using "security" test scenarios here, *not* "securityAndSpaces", because of how these tests are structured. + + [ + users.noAccess, + users.legacyAll, + users.dualRead, + users.readGlobally, + users.allAtDefaultSpace, + users.readAtDefaultSpace, + users.readAtSpace1, + ].forEach((user) => { + const unauthorized = createTestDefinitions(createTestCases(SPACE_1_ID, SPACE_2_ID), true); + _addTests(user, unauthorized); + }); + + const authorizedSpace1 = [ + ...createTestDefinitions(createTestCases(SPACE_1_ID), false), + ...createTestDefinitions(createTestCases(SPACE_2_ID), true), + ]; + _addTests(users.allAtSpace1, authorizedSpace1); + + [users.dualAll, users.allGlobally, users.superuser].forEach((user) => { + const authorizedGlobally = createTestDefinitions( + createTestCases(SPACE_1_ID, SPACE_2_ID), + false + ); + _addTests(user, authorizedGlobally); + }); + }); + }); +} diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts index 4bb4d10eaabf8..a86fef0d758fc 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/index.ts @@ -29,5 +29,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./update_objects_spaces')); + loadTestFile(require.resolve('./disable_legacy_url_aliases')); }); } diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts new file mode 100644 index 0000000000000..4cb73a7849b43 --- /dev/null +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/disable_legacy_url_aliases.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SPACES } from '../../common/lib/spaces'; +import { + disableLegacyUrlAliasesTestSuiteFactory, + DisableLegacyUrlAliasesTestCase, + TEST_CASE_TARGET_TYPE, + TEST_CASE_SOURCE_ID, +} from '../../common/suites/disable_legacy_url_aliases'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +const { + SPACE_1: { spaceId: SPACE_1_ID }, + SPACE_2: { spaceId: SPACE_2_ID }, +} = SPACES; + +const createTestCases = (...spaceIds: string[]): DisableLegacyUrlAliasesTestCase[] => { + return spaceIds.map((targetSpace) => ({ + targetSpace, + targetType: TEST_CASE_TARGET_TYPE, + sourceId: TEST_CASE_SOURCE_ID, + })); +}; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + const { addTests, createTestDefinitions } = disableLegacyUrlAliasesTestSuiteFactory( + es, + esArchiver, + supertest + ); + + const testCases = createTestCases(SPACE_1_ID, SPACE_2_ID); + const tests = createTestDefinitions(testCases, false); + addTests(`_disable_legacy_url_aliases`, { tests }); +} diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts index 489e2c2d22ffa..f64336b2b4908 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/index.ts @@ -21,5 +21,6 @@ export default function spacesOnlyTestSuite({ loadTestFile }: FtrProviderContext loadTestFile(require.resolve('./get')); loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./update_objects_spaces')); + loadTestFile(require.resolve('./disable_legacy_url_aliases')); }); } From bf6be219e8c2e306cd0a20c8c0ff79daa0d7608e Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 28 Jun 2021 16:27:41 -0600 Subject: [PATCH 26/74] [Maps] deprecate 'map.regionmap' kibana config and 'Configured GeoJSON' source (#103373) * [Maps] deprecate 'map.regionmap' kibana config and 'Configured GeoJSON' source * clean up message * revert change to KibanaRegionmapSource.getGeoJsonWithMeta * tslint * doc updates * clean up Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/maps/connect-to-ems.asciidoc | 1 - docs/setup/settings.asciidoc | 28 +++++----- x-pack/plugins/maps/common/constants.ts | 5 -- .../classes/layers/load_layer_wizards.ts | 4 -- .../layers/vector_layer/vector_layer.tsx | 14 +++-- .../create_source_editor.js | 55 ------------------ .../kibana_regionmap_source/fetch_geojson.ts | 56 +++++++++++++++++++ .../sources/kibana_regionmap_source/index.js | 1 - .../kibana_regionmap_layer_wizard.tsx | 39 ------------- .../kibana_regionmap_source.ts | 29 ++++++++-- .../sources/vector_source/vector_source.tsx | 1 + x-pack/plugins/maps/public/util.ts | 56 +------------------ x-pack/plugins/maps/server/index.ts | 36 ++++++++++++ .../translations/translations/ja-JP.json | 4 -- .../translations/translations/zh-CN.json | 4 -- 15 files changed, 144 insertions(+), 189 deletions(-) delete mode 100644 x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js create mode 100644 x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts delete mode 100644 x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc index a54da6597b9b0..1db9dd5ee1123 100644 --- a/docs/maps/connect-to-ems.asciidoc +++ b/docs/maps/connect-to-ems.asciidoc @@ -34,7 +34,6 @@ To disable EMS, change your <> file. . Set `map.includeElasticMapsService` to `false` to turn off the EMS connection. . Set `map.tilemap.url` to the URL of your tile server. This configures the default tile layer of Maps. -. (Optional) Set `map.regionmap` to the vector shapes of the administrative boundaries that you want to use. [float] [id=elastic-maps-server] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index bcaa86d73adc4..696b2f042057d 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -368,8 +368,7 @@ The time interval policy will rotate the log file every given interval of time. | [[regionmap-ES-map]] `map.includeElasticMapsService:` {ess-icon} | Set to `false` to disable connections to Elastic Maps Service. -When `includeElasticMapsService` is turned off, only the vector layers configured by <> -and the tile layer configured by <> are available in <>. *Default: `true`* +When `includeElasticMapsService` is turned off, only tile layer configured by <> is available in <>. *Default: `true`* | `map.emsUrl:` | Specifies the URL of a self hosted <> @@ -379,7 +378,8 @@ and the tile layer configured by <> are availabl requests through the {kib} server. *Default: `false`* | [[regionmap-settings]] `map.regionmap:` {ess-icon} - | Specifies additional vector layers for + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Specifies additional vector layers for use in <> visualizations. Each layer object points to an external vector file that contains a geojson FeatureCollection. The file must use the @@ -393,7 +393,6 @@ The following example shows a valid region map configuration. [source,text] -- map.regionmap: - includeElasticMapsService: false layers: - name: "Departments of France" url: "http://my.cors.enabled.server.org/france_departements.geojson" @@ -409,10 +408,12 @@ map.regionmap: |=== | [[regionmap-attribution]] `map.regionmap.layers[].attribution:` {ess-icon} - | Optional. References the originating source of the geojson file. + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Optional. References the originating source of the geojson file. | [[regionmap-fields]] `map.regionmap.layers[].fields[]:` {ess-icon} - | Mandatory. Each layer + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Mandatory. Each layer can contain multiple fields to indicate what properties from the geojson features you wish to expose. The following shows how to define multiple properties: @@ -422,7 +423,6 @@ properties: [source,text] -- map.regionmap: - includeElasticMapsService: false layers: - name: "Departments of France" url: "http://my.cors.enabled.server.org/france_departements.geojson" @@ -438,11 +438,13 @@ map.regionmap: |=== | [[regionmap-field-description]] `map.regionmap.layers[].fields[].description:` {ess-icon} - | Mandatory. The human readable text that is shown under the Options tab when + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Mandatory. The human readable text that is shown under the Options tab when building the Region Map visualization. | [[regionmap-field-name]] `map.regionmap.layers[].fields[].name:` {ess-icon} - | Mandatory. + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Mandatory. This value is used to do an inner-join between the document stored in {es} and the geojson file. For example, if the field in the geojson is called `Location` and has city names, there must be a field in {es} @@ -450,12 +452,12 @@ that holds the same values that {kib} can then use to lookup for the geoshape data. | [[regionmap-name]] `map.regionmap.layers[].name:` {ess-icon} - | Mandatory. A description of -the map being provided. + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Mandatory. A description of the map being provided. | [[regionmap-url]] `map.regionmap.layers[].url:` {ess-icon} - | Mandatory. The location of the -geojson file as provided by a webserver. + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + Mandatory. The location of the geojson file as provided by a webserver. | [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon} | The map attribution string. diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index c16579cc142f0..e1ec3e269b333 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -265,11 +265,6 @@ export enum SCALING_TYPES { MVT = 'MVT', } -export enum FORMAT_TYPE { - GEOJSON = 'geojson', - TOPOJSON = 'topojson', -} - export enum MVT_FIELD_TYPE { STRING = 'String', NUMBER = 'Number', diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index 6ee863cfdb600..2c7f09ce9dfeb 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -20,8 +20,6 @@ import { emsBoundariesLayerWizardConfig } from '../sources/ems_file_source'; // @ts-ignore import { emsBaseMapLayerWizardConfig } from '../sources/ems_tms_source'; // @ts-ignore -import { kibanaRegionMapLayerWizardConfig } from '../sources/kibana_regionmap_source'; -// @ts-ignore import { kibanaBasemapLayerWizardConfig } from '../sources/kibana_tilemap_source'; import { tmsLayerWizardConfig } from '../sources/xyz_tms_source'; // @ts-ignore @@ -59,8 +57,6 @@ export function registerLayerWizards() { // @ts-ignore registerLayerWizard(emsBaseMapLayerWizardConfig); // @ts-ignore - registerLayerWizard(kibanaRegionMapLayerWizardConfig); - // @ts-ignore registerLayerWizard(kibanaBasemapLayerWizardConfig); registerLayerWizard(tmsLayerWizardConfig); // @ts-ignore diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 7a6d91a71db42..f8d141574e5f0 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -236,11 +236,17 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const sourceDataRequest = this.getSourceDataRequest(); - const { tooltipContent, areResultsTrimmed } = this.getSource().getSourceTooltipContent( - sourceDataRequest - ); + const { + tooltipContent, + areResultsTrimmed, + isDeprecated, + } = this.getSource().getSourceTooltipContent(sourceDataRequest); return { - icon: this.getCurrentStyle().getIcon(), + icon: isDeprecated ? ( + + ) : ( + this.getCurrentStyle().getIcon() + ), tooltipContent, areResultsTrimmed, }; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js deleted file mode 100644 index 1278d84f103da..0000000000000 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/create_source_editor.js +++ /dev/null @@ -1,55 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiSelect, EuiFormRow, EuiPanel } from '@elastic/eui'; -import { getKibanaRegionList } from '../../../util'; -import { i18n } from '@kbn/i18n'; - -export function CreateSourceEditor({ onSourceConfigChange }) { - const onChange = ({ target }) => { - const selectedName = target.options[target.selectedIndex].text; - onSourceConfigChange({ name: selectedName }); - }; - - const regionmapOptions = getKibanaRegionList().map(({ name, url }) => { - return { - value: url, - text: name, - }; - }); - - const helpText = - regionmapOptions.length === 0 - ? i18n.translate('xpack.maps.source.kbnRegionMap.noLayerAvailableHelptext', { - defaultMessage: `No vector layers are available. Ask your system administrator to set "map.regionmap" in kibana.yml.`, - }) - : null; - - return ( - - - - - - ); -} - -CreateSourceEditor.propTypes = { - onSourceConfigChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts new file mode 100644 index 0000000000000..329070632a94d --- /dev/null +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/fetch_geojson.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { FeatureCollection } from 'geojson'; +import * as topojson from 'topojson-client'; +import { GeometryCollection } from 'topojson-specification'; +import fetch from 'node-fetch'; + +export enum FORMAT_TYPE { + GEOJSON = 'geojson', + TOPOJSON = 'topojson', +} + +export async function fetchGeoJson( + fetchUrl: string, + format: FORMAT_TYPE, + featureCollectionPath: string +): Promise { + let fetchedJson; + try { + const response = await fetch(fetchUrl); + if (!response.ok) { + throw new Error('Request failed'); + } + fetchedJson = await response.json(); + } catch (e) { + throw new Error( + i18n.translate('xpack.maps.util.requestFailedErrorMessage', { + defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`, + values: { fetchUrl }, + }) + ); + } + + if (format === FORMAT_TYPE.GEOJSON) { + return fetchedJson; + } + + if (format === FORMAT_TYPE.TOPOJSON) { + const features = _.get(fetchedJson, `objects.${featureCollectionPath}`) as GeometryCollection; + return topojson.feature(fetchedJson, features); + } + + throw new Error( + i18n.translate('xpack.maps.util.formatErrorMessage', { + defaultMessage: `Unable to fetch vector shapes from url: {format}`, + values: { format }, + }) + ); +} diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js index f1e54ae0db318..dcf009d0a280b 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/index.js @@ -5,5 +5,4 @@ * 2.0. */ -export { kibanaRegionMapLayerWizardConfig } from './kibana_regionmap_layer_wizard'; export { KibanaRegionmapSource } from './kibana_regionmap_source'; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx deleted file mode 100644 index 9091e03fdf7f5..0000000000000 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry'; -// @ts-ignore -import { KibanaRegionmapSource, sourceTitle } from './kibana_regionmap_source'; -import { VectorLayer } from '../../layers/vector_layer'; -// @ts-ignore -import { CreateSourceEditor } from './create_source_editor'; -import { getKibanaRegionList } from '../../../util'; -import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants'; - -export const kibanaRegionMapLayerWizardConfig: LayerWizard = { - categories: [LAYER_WIZARD_CATEGORY.REFERENCE], - checkVisibility: async () => { - const regions = getKibanaRegionList(); - return regions.length > 0; - }, - description: i18n.translate('xpack.maps.source.kbnRegionMapDescription', { - defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml', - }), - icon: 'logoKibana', - renderWizard: ({ previewLayers, mapColors }: RenderWizardArguments) => { - const onSourceConfigChange = (sourceConfig: { name: string }) => { - const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig); - const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); - previewLayers([layerDescriptor]); - }; - - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts index b0241876e5728..f7f311d011d6e 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/kibana_regionmap_source/kibana_regionmap_source.ts @@ -7,17 +7,18 @@ import { i18n } from '@kbn/i18n'; import { AbstractVectorSource, GeoJsonWithMeta } from '../vector_source'; -import { fetchGeoJson, getKibanaRegionList } from '../../../util'; +import { getRegionmapLayers } from '../../../kibana_services'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; -import { FIELD_ORIGIN, FORMAT_TYPE, SOURCE_TYPES } from '../../../../common/constants'; +import { FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants'; import { KibanaRegionField } from '../../fields/kibana_region_field'; import { registerSource } from '../source_registry'; import { KibanaRegionmapSourceDescriptor } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { IField } from '../../fields/field'; import type { LayerConfig } from '../../../../../../../src/plugins/maps_ems/public'; +import { fetchGeoJson, FORMAT_TYPE } from './fetch_geojson'; -export const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { +const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { defaultMessage: 'Configured GeoJSON', }); @@ -45,6 +46,7 @@ export class KibanaRegionmapSource extends AbstractVectorSource { } async getImmutableProperties() { + const vectorFileMeta = await this.getVectorFileMeta(); return [ { label: getDataSourceLabel(), @@ -56,11 +58,17 @@ export class KibanaRegionmapSource extends AbstractVectorSource { }), value: this._descriptor.name, }, + { + label: i18n.translate('xpack.maps.source.kbnRegionMap.vectorLayerUrlLabel', { + defaultMessage: 'Vector layer url', + }), + value: vectorFileMeta.url, + }, ]; } async getVectorFileMeta(): Promise { - const regionList: LayerConfig[] = getKibanaRegionList(); + const regionList: LayerConfig[] = getRegionmapLayers(); const layerConfig: LayerConfig | undefined = regionList.find( (regionConfig: LayerConfig) => regionConfig.name === this._descriptor.name ); @@ -107,6 +115,19 @@ export class KibanaRegionmapSource extends AbstractVectorSource { hasTooltipProperties() { return true; } + + getSourceTooltipContent() { + return { + tooltipContent: i18n.translate('xpack.maps.source.kbnRegionMap.deprecationTooltipMessage', { + defaultMessage: `'Configured GeoJSON' layer is deprecated. 1) Use 'Upload GeoJSON' to upload '{vectorLayer}'. 2) Use Choropleth layer wizard to build a replacement layer. 3) Finally, delete this layer from your map.`, + values: { + vectorLayer: this._descriptor.name, + }, + }), + areResultsTrimmed: false, + isDeprecated: true, + }; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index f006fa7fde3a4..7dc816f6e1b6c 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -24,6 +24,7 @@ import { DataRequest } from '../../util/data_request'; export interface SourceTooltipConfig { tooltipContent: string | null; areResultsTrimmed: boolean; + isDeprecated?: boolean; } export type GeoJsonFetchMeta = ESSearchSourceResponseMeta; diff --git a/x-pack/plugins/maps/public/util.ts b/x-pack/plugins/maps/public/util.ts index 16743c0049116..f92a60ffedfdc 100644 --- a/x-pack/plugins/maps/public/util.ts +++ b/x-pack/plugins/maps/public/util.ts @@ -7,11 +7,7 @@ import { i18n } from '@kbn/i18n'; import { EMSClient, FileLayer, TMSService } from '@elastic/ems-client'; -import { FeatureCollection } from 'geojson'; -import * as topojson from 'topojson-client'; -import { GeometryCollection } from 'topojson-specification'; import _ from 'lodash'; -import fetch from 'node-fetch'; import { GIS_API_PATH, EMS_FILES_CATALOGUE_PATH, @@ -19,21 +15,9 @@ import { EMS_GLYPHS_PATH, EMS_APP_NAME, FONTS_API_PATH, - FORMAT_TYPE, } from '../common/constants'; -import { - getHttp, - getRegionmapLayers, - getTilemap, - getKibanaVersion, - getEMSSettings, -} from './kibana_services'; +import { getHttp, getTilemap, getKibanaVersion, getEMSSettings } from './kibana_services'; import { getLicenseId } from './licensed_features'; -import { LayerConfig } from '../../../../src/plugins/maps_ems/public'; - -export function getKibanaRegionList(): LayerConfig[] { - return getRegionmapLayers(); -} export function getKibanaTileMap(): unknown { return getTilemap(); @@ -117,41 +101,3 @@ export function getGlyphUrl(): string { export function isRetina(): boolean { return window.devicePixelRatio === 2; } - -export async function fetchGeoJson( - fetchUrl: string, - format: FORMAT_TYPE, - featureCollectionPath: string -): Promise { - let fetchedJson; - try { - const response = await fetch(fetchUrl); - if (!response.ok) { - throw new Error('Request failed'); - } - fetchedJson = await response.json(); - } catch (e) { - throw new Error( - i18n.translate('xpack.maps.util.requestFailedErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {fetchUrl}`, - values: { fetchUrl }, - }) - ); - } - - if (format === FORMAT_TYPE.GEOJSON) { - return fetchedJson; - } - - if (format === FORMAT_TYPE.TOPOJSON) { - const features = _.get(fetchedJson, `objects.${featureCollectionPath}`) as GeometryCollection; - return topojson.feature(fetchedJson, features); - } - - throw new Error( - i18n.translate('xpack.maps.util.formatErrorMessage', { - defaultMessage: `Unable to fetch vector shapes from url: {format}`, - values: { format }, - }) - ); -} diff --git a/x-pack/plugins/maps/server/index.ts b/x-pack/plugins/maps/server/index.ts index fecad3ae5cf97..665fbc19b0a90 100644 --- a/x-pack/plugins/maps/server/index.ts +++ b/x-pack/plugins/maps/server/index.ts @@ -5,6 +5,9 @@ * 2.0. */ +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { AddConfigDeprecation } from '@kbn/config'; import { PluginInitializerContext } from 'src/core/server'; import { PluginConfigDescriptor } from 'kibana/server'; import { MapsPlugin } from './plugin'; @@ -21,6 +24,39 @@ export const config: PluginConfigDescriptor = { preserveDrawingBuffer: true, }, schema: configSchema, + deprecations: () => [ + ( + completeConfig: Record, + rootPath: string, + addDeprecation: AddConfigDeprecation + ) => { + if (_.get(completeConfig, 'map.regionmap') === undefined) { + return completeConfig; + } + addDeprecation({ + message: i18n.translate('xpack.maps.deprecation.regionmap.message', { + defaultMessage: 'map.regionmap is deprecated and will be removed in 8.0.', + }), + correctiveActions: { + manualSteps: [ + i18n.translate('xpack.maps.deprecation.regionmap.step1', { + defaultMessage: + 'Remove "map.regionmap" in the Kibana config file, CLI flag, or environment variable (in Docker only).', + }), + i18n.translate('xpack.maps.deprecation.regionmap.step2', { + defaultMessage: + 'Use "Upload GeoJSON" to upload each layer defined by "map.regionmap.layers".', + }), + i18n.translate('xpack.maps.deprecation.regionmap.step3', { + defaultMessage: + 'Update all maps with "Configured GeoJSON" layers. Use Choropleth layer wizard to build a replacement layer. Delete "Configured GeoJSON" layer from your map.', + }), + ], + }, + }); + return completeConfig; + }, + ], }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cd2fafb0abbc0..ca2be10624e66 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13432,9 +13432,7 @@ "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "並べ替え順", "xpack.maps.source.geofieldLabel": "地理空間フィールド", "xpack.maps.source.kbnRegionMap.noConfigErrorMessage": "{name} の map.regionmap 構成が見つかりません", - "xpack.maps.source.kbnRegionMap.noLayerAvailableHelptext": "ベクターレイヤーが利用できません。システム管理者に、kibana.yml で「map.regionmap」を設定するよう依頼してください。", "xpack.maps.source.kbnRegionMap.vectorLayerLabel": "ベクターレイヤー", - "xpack.maps.source.kbnRegionMapDescription": "kibana.yml で構成された静的ファイルのベクターシェイプです", "xpack.maps.source.kbnRegionMapTitle": "カスタムベクターシェイプ", "xpack.maps.source.kbnTMS.kbnTMS.urlLabel": "タイルマップ URL", "xpack.maps.source.kbnTMS.noConfigErrorMessage": "kibana.yml に map.tilemap.url 構成が見つかりません", @@ -13597,8 +13595,6 @@ "xpack.maps.tutorials.ems.shortDescription": "Elastic Maps Service からの管理ベクターシェイプ。", "xpack.maps.tutorials.ems.uploadStepText": "1.[Maps] ({newMapUrl}) を開きます。\n2.[Add layer]をクリックしてから[Upload GeoJSON]を選択します。\n3.GeoJSON ファイルをアップロードして[Import file]をクリックします。", "xpack.maps.tutorials.ems.uploadStepTitle": "Elastic Maps Service境界のインデックス作成", - "xpack.maps.util.formatErrorMessage": "URL からベクターシェイプを取得できません:{format}", - "xpack.maps.util.requestFailedErrorMessage": "URL からベクターシェイプを取得できません:{fetchUrl}", "xpack.maps.validatedNumberInput.invalidClampErrorMessage": "{min} と {max} の間でなければなりません", "xpack.maps.validatedRange.rangeErrorMessage": "{min} と {max} の間でなければなりません", "xpack.maps.vector.dualSize.unitLabel": "px", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2522abd40f07f..341741dd4046a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13610,9 +13610,7 @@ "xpack.maps.source.esTopHitsSearch.sortOrderLabel": "排序顺序", "xpack.maps.source.geofieldLabel": "地理空间字段", "xpack.maps.source.kbnRegionMap.noConfigErrorMessage": "找不到 {name} 的 map.regionmap 配置", - "xpack.maps.source.kbnRegionMap.noLayerAvailableHelptext": "没有可用的矢量图层。让您的系统管理员在 kibana.yml 中设置“map.regionmap”。", "xpack.maps.source.kbnRegionMap.vectorLayerLabel": "矢量图层", - "xpack.maps.source.kbnRegionMapDescription": "来自 kibana.yml 配置的静态文件的矢量形状", "xpack.maps.source.kbnRegionMapTitle": "定制矢量形状", "xpack.maps.source.kbnTMS.kbnTMS.urlLabel": "磁贴地图 URL", "xpack.maps.source.kbnTMS.noConfigErrorMessage": "在 kibana.yml 中找不到 map.tilemap.url 配置", @@ -13775,8 +13773,6 @@ "xpack.maps.tutorials.ems.shortDescription": "来自 Elastic Maps Service 的管理边界。", "xpack.maps.tutorials.ems.uploadStepText": "1.打开 [Maps]({newMapUrl}).\n2.单击`添加图层`,然后选择`上传 GeoJSON`。\n3.上传 GeoJSON 文件,然后单击`导入文件`。", "xpack.maps.tutorials.ems.uploadStepTitle": "索引 Elastic Maps Service 边界", - "xpack.maps.util.formatErrorMessage": "无法从以下 URL 获取矢量形状:{format}", - "xpack.maps.util.requestFailedErrorMessage": "无法从以下 URL 获取矢量形状:{fetchUrl}", "xpack.maps.validatedNumberInput.invalidClampErrorMessage": "必须介于 {min} 和 {max} 之间", "xpack.maps.validatedRange.rangeErrorMessage": "必须介于 {min} 和 {max} 之间", "xpack.maps.vector.dualSize.unitLabel": "px", From b6fb390ea9941365d94e1c4404af54cd6b494308 Mon Sep 17 00:00:00 2001 From: James Rucker Date: Mon, 28 Jun 2021 15:27:49 -0700 Subject: [PATCH 27/74] [Workplace Search] OAuth flows for Custom Search and Default Search (#101996) * Add OAuth authorize endpoint support for custom search experiences * Add support for default search experience authentication Co-authored-by: scottybollinger --- .../common/__mocks__/initial_app_data.ts | 4 + .../enterprise_search/common/types/index.ts | 6 + .../workplace_search/app_logic.test.ts | 5 + .../workplace_search/app_logic.ts | 16 +- .../applications/workplace_search/index.tsx | 10 ++ .../applications/workplace_search/routes.ts | 3 + .../views/oauth_authorize/constants.ts | 57 ++++++ .../views/oauth_authorize/index.ts | 8 + .../oauth_authorize/oauth_authorize.test.tsx | 115 ++++++++++++ .../views/oauth_authorize/oauth_authorize.tsx | 169 ++++++++++++++++++ .../oauth_authorize_logic.test.ts | 156 ++++++++++++++++ .../oauth_authorize/oauth_authorize_logic.ts | 168 +++++++++++++++++ .../views/search_authorize/index.ts | 8 + .../search_authorize.test.tsx | 64 +++++++ .../search_authorize/search_authorize.tsx | 45 +++++ .../search_authorize_logic.test.ts | 113 ++++++++++++ .../search_authorize_logic.ts | 112 ++++++++++++ .../lib/enterprise_search_config_api.test.ts | 8 + .../lib/enterprise_search_config_api.ts | 4 + .../server/routes/workplace_search/index.ts | 2 + .../routes/workplace_search/oauth.test.ts | 143 +++++++++++++++ .../server/routes/workplace_search/oauth.ts | 89 +++++++++ 22 files changed, 1303 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts diff --git a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts index a4e3ada1c06cb..22ee6a246bad6 100644 --- a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts +++ b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts @@ -8,6 +8,10 @@ export const DEFAULT_INITIAL_APP_DATA = { readOnlyMode: false, ilmEnabled: true, + searchOAuth: { + clientId: 'someUID', + redirectUrl: 'http://localhost:3002/ws/search_callback', + }, configuredLimits: { appSearch: { engine: { diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index f405c86de18f0..b0b9eb6274875 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -17,6 +17,7 @@ import { export interface InitialAppData { readOnlyMode?: boolean; ilmEnabled?: boolean; + searchOAuth?: SearchOAuth; configuredLimits?: ConfiguredLimits; access?: ProductAccess; appSearch?: AppSearchAccount; @@ -33,6 +34,11 @@ export interface ProductAccess { hasWorkplaceSearchAccess: boolean; } +export interface SearchOAuth { + clientId: string; + redirectUrl: string; +} + export interface MetaPage { current: number; size: number; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index 24a156bbd67b8..2d3570e6e0ebf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -21,6 +21,7 @@ describe('AppLogic', () => { account: {}, hasInitialized: false, isOrganization: false, + searchOAuth: {}, organization: {}, }; @@ -36,6 +37,10 @@ describe('AppLogic', () => { }, hasInitialized: true, isOrganization: false, + searchOAuth: { + clientId: 'someUID', + redirectUrl: 'http://localhost:3002/ws/search_callback', + }, organization: { defaultOrgName: 'My Organization', name: 'ACME Donuts', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts index ee1f6a69fa4b9..0e1edd807d47a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.ts @@ -7,7 +7,8 @@ import { kea, MakeLogicType } from 'kea'; -import { InitialAppData } from '../../../common/types'; +import { InitialAppData, SearchOAuth } from '../../../common/types'; + import { Organization, WorkplaceSearchInitialData, @@ -17,6 +18,7 @@ import { interface AppValues extends WorkplaceSearchInitialData { hasInitialized: boolean; isOrganization: boolean; + searchOAuth: SearchOAuth; } interface AppActions { initializeAppData(props: InitialAppData): InitialAppData; @@ -27,11 +29,15 @@ interface AppActions { const emptyOrg = {} as Organization; const emptyAccount = {} as Account; +const emptySearchOAuth = {} as SearchOAuth; export const AppLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'app_logic'], actions: { - initializeAppData: ({ workplaceSearch }) => ({ workplaceSearch }), + initializeAppData: ({ workplaceSearch, searchOAuth }) => ({ + workplaceSearch, + searchOAuth, + }), setContext: (isOrganization) => isOrganization, setOrgName: (name: string) => name, setSourceRestriction: (canCreatePersonalSources: boolean) => canCreatePersonalSources, @@ -69,5 +75,11 @@ export const AppLogic = kea>({ }), }, ], + searchOAuth: [ + emptySearchOAuth, + { + initializeAppData: (_, { searchOAuth }) => searchOAuth || emptySearchOAuth, + }, + ], }, }); 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 2daf677962163..432a5125f6b56 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 @@ -19,8 +19,10 @@ import { WorkplaceSearchHeaderActions } from './components/layout'; import { GROUPS_PATH, SETUP_GUIDE_PATH, + SEARCH_AUTHORIZE_PATH, SOURCES_PATH, SOURCE_ADDED_PATH, + OAUTH_AUTHORIZE_PATH, PERSONAL_SOURCES_PATH, ORG_SETTINGS_PATH, USERS_AND_ROLES_PATH, @@ -34,8 +36,10 @@ import { SourceAdded } from './views/content_sources/components/source_added'; import { ErrorState } from './views/error_state'; import { GroupsRouter } from './views/groups'; import { NotFound } from './views/not_found'; +import { OAuthAuthorize } from './views/oauth_authorize'; import { Overview } from './views/overview'; import { RoleMappings } from './views/role_mappings'; +import { SearchAuthorize } from './views/search_authorize'; import { Security } from './views/security'; import { SettingsRouter } from './views/settings'; import { SetupGuide } from './views/setup_guide'; @@ -96,6 +100,12 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { + + + + + + 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 3dac5f80700c0..edd33ac885569 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 @@ -48,6 +48,9 @@ export const ENT_SEARCH_LICENSE_MANAGEMENT = `${docLinks.enterpriseSearchBase}/l export const PERSONAL_PATH = '/p'; +export const OAUTH_AUTHORIZE_PATH = `${PERSONAL_PATH}/oauth/authorize`; +export const SEARCH_AUTHORIZE_PATH = `${PERSONAL_PATH}/authorize_search`; + export const USERS_AND_ROLES_PATH = '/users_and_roles'; export const SECURITY_PATH = '/security'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/constants.ts new file mode 100644 index 0000000000000..5b283a6652586 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/constants.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const HTTP_REDIRECT_WARNING_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.httpRedirectWarningMessage', + { + defaultMessage: 'This application is using an insecure redirect URI (http)', + } +); + +export const SCOPES_LEAD_IN_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.scopesLeadInMessage', + { + defaultMessage: 'This application will be able to', + } +); + +export const AUTHORIZATION_REQUIRED_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationTitle', + { + defaultMessage: 'Authorization required', + } +); + +export const AUTHORIZE_BUTTON_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizeButtonLabel', + { + defaultMessage: 'Authorize', + } +); + +export const DENY_BUTTON_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.denyButtonLabel', + { + defaultMessage: 'Deny', + } +); + +export const SEARCH_SCOPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.searchScopeDescription', + { + defaultMessage: 'Search your data', + } +); + +export const WRITE_SCOPE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.writeScopeDescription', + { + defaultMessage: 'Modify your data', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/index.ts new file mode 100644 index 0000000000000..613d57901653b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { OAuthAuthorize } from './oauth_authorize'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.test.tsx new file mode 100644 index 0000000000000..083c3e6114547 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.test.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../__mocks__/react_router'; +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; + +import React from 'react'; +import { useLocation } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { EuiCallOut } from '@elastic/eui'; + +import { Loading } from '../../../shared/loading'; + +import { OAuthAuthorize } from './oauth_authorize'; + +describe('OAuthAuthorize', () => { + const mockActions = { + initializeSearchAuth: jest.fn(), + initializeOAuthPreAuth: jest.fn(), + allowOAuthAuthorization: jest.fn(), + denyOAuthAuthorization: jest.fn(), + }; + + const mockValues = { + dataLoading: true, + buttonLoading: false, + cachedPreAuth: {}, + hasError: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(mockValues); + setMockActions(mockActions); + }); + + it('renders loading and calls initializeSearchAuth', () => { + const search = '?state=someRandomString'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + expect(mockActions.initializeOAuthPreAuth).toHaveBeenCalled(); + }); + + it('renders httpRedirectUriWarning', () => { + setMockValues({ + ...mockValues, + dataLoading: false, + cachedPreAuth: { + redirectUri: 'http://foo', + }, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiCallOut)).toHaveLength(2); + }); + + describe('scopeDescription', () => { + it('renders "search" scope description', () => { + setMockValues({ + ...mockValues, + dataLoading: false, + cachedPreAuth: { + scopes: ['search'], + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="ScopeDescription"]').text()).toContain( + 'Search your data' + ); + }); + + it('renders "write" scope description', () => { + setMockValues({ + ...mockValues, + dataLoading: false, + cachedPreAuth: { + scopes: ['write'], + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="ScopeDescription"]').text()).toContain( + 'Modify your data' + ); + }); + + it('renders "unknown" scope description', () => { + setMockValues({ + ...mockValues, + dataLoading: false, + cachedPreAuth: { + scopes: ['other'], + }, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="ScopeDescription"]').text()).toContain( + '' + ); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx new file mode 100644 index 0000000000000..972f1951d47b8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { Location } from 'history'; +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiCallOut, + EuiHeaderSection, + EuiHeaderSectionItem, + EuiHeaderLogo, + EuiPage, + EuiPageBody, + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiText, + EuiSpacer, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { Loading } from '../../../shared/loading'; + +import { WORKPLACE_SEARCH_TITLE } from '../../constants'; + +import { + AUTHORIZATION_REQUIRED_TITLE, + AUTHORIZE_BUTTON_LABEL, + DENY_BUTTON_LABEL, + HTTP_REDIRECT_WARNING_MESSAGE, + SCOPES_LEAD_IN_MESSAGE, + SEARCH_SCOPE_DESCRIPTION, + WRITE_SCOPE_DESCRIPTION, +} from './constants'; + +import { OAuthAuthorizeLogic } from './oauth_authorize_logic'; + +export const OAuthAuthorize: React.FC = () => { + const { search } = useLocation() as Location; + const { initializeOAuthPreAuth, allowOAuthAuthorization, denyOAuthAuthorization } = useActions( + OAuthAuthorizeLogic + ); + + const { buttonLoading, dataLoading, cachedPreAuth, hasError } = useValues(OAuthAuthorizeLogic); + + useEffect(() => { + initializeOAuthPreAuth(search); + }, []); + + if (dataLoading) return ; + + const showHttpRedirectUriWarning = cachedPreAuth.redirectUri?.startsWith('http:'); + + const httpRedirectUriWarning = ( + <> + + + + ); + + const scopeDescription = (scopeName: string): string | undefined => { + if (scopeName === 'search') { + return SEARCH_SCOPE_DESCRIPTION; + } else if (scopeName === 'write') { + return WRITE_SCOPE_DESCRIPTION; + } else { + return undefined; + } + }; + + const scopeListItems = cachedPreAuth?.scopes?.map((scope, i) => { + const scopeDesc = scopeDescription(scope); + const unknownScopeDesc = ( + + ); + + return ( +
  • + {scopeDesc || unknownScopeDesc} +
  • + ); + }); + + const authorizationDetails = ( + <> + + +

    + {cachedPreAuth.clientName}, + }} + /> +

    +
    + + {showHttpRedirectUriWarning && httpRedirectUriWarning} + +
      {scopeListItems}
    +
    + + ); + + return ( + + + + + + + + + +

    {WORKPLACE_SEARCH_TITLE}

    +
    +
    +
    + + +

    {AUTHORIZATION_REQUIRED_TITLE}

    +
    + {!hasError && authorizationDetails} + + + + + + {DENY_BUTTON_LABEL} + + + + + {AUTHORIZE_BUTTON_LABEL} + + + +
    +
    +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts new file mode 100644 index 0000000000000..3d31cb7d88225 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, +} from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test/jest'; + +import { OAuthAuthorizeLogic, transformServerPreAuth } from './oauth_authorize_logic'; + +describe('OAuthAuthorizeLogic', () => { + const { http } = mockHttpValues; + const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers; + const { mount } = new LogicMounter(OAuthAuthorizeLogic); + const defaultValues = { + dataLoading: true, + buttonLoading: false, + cachedPreAuth: {}, + hasError: false, + }; + const serverResponse = { + client_id: 'id1', + client_name: 'client1', + redirect_uri: 'htt://foo.bar', + response_type: 'JSON', + scope: 'scope1', + state: 'state1', + status: 'success', + }; + const entSearchState = 'entSearchStateString'; + const entSearchStateParam = `state=${entSearchState}`; + const searchOAuth = { + clientId: 'someUID', + redirectUrl: 'http://localhost:3002/ws/search_callback', + }; + const successRedirectResponse = { + redirect_uri: `${searchOAuth.redirectUrl}?code=authCode&${entSearchStateParam}`, + status: 'redirect', + }; + + const parsedQueryParams = { + state: 'entSearchStateString', + }; + + beforeEach(() => { + jest.clearAllMocks(); + mount(); + }); + + it('has expected default values', () => { + expect(OAuthAuthorizeLogic.values).toEqual(defaultValues); + }); + + describe('actions', () => { + it('setServerProps', () => { + OAuthAuthorizeLogic.actions.setServerProps(serverResponse); + + expect(OAuthAuthorizeLogic.values).toEqual({ + ...defaultValues, + dataLoading: false, + cachedPreAuth: { + ...transformServerPreAuth(serverResponse), + }, + }); + }); + + it('setButtonNotLoading', () => { + // Set loading state to true for test. + OAuthAuthorizeLogic.actions.allowOAuthAuthorization(); + OAuthAuthorizeLogic.actions.setButtonNotLoading(); + + expect(OAuthAuthorizeLogic.values).toEqual({ + ...defaultValues, + buttonLoading: false, + }); + }); + + it('allowOAuthAuthorization', () => { + OAuthAuthorizeLogic.actions.allowOAuthAuthorization(); + + expect(OAuthAuthorizeLogic.values).toEqual({ + ...defaultValues, + buttonLoading: true, + }); + }); + + it('denyOAuthAuthorization', () => { + OAuthAuthorizeLogic.actions.denyOAuthAuthorization(); + + expect(OAuthAuthorizeLogic.values).toEqual({ + ...defaultValues, + buttonLoading: true, + }); + }); + }); + + describe('listeners', () => { + describe('initializeOAuthPreAuth', () => { + // Mocking window.location.replace(redirectUrl) for redirects + const mockLocationReplace = jest.fn(); + const mockLocation = { + value: { + replace: mockLocationReplace, + }, + writable: true, + }; + Object.defineProperty(window, 'location', mockLocation); + + it('gets pre-authorization and sets values', async () => { + const setServerPropsSpy = jest.spyOn(OAuthAuthorizeLogic.actions, 'setServerProps'); + http.get.mockReturnValue(Promise.resolve(serverResponse)); + OAuthAuthorizeLogic.actions.initializeOAuthPreAuth(entSearchStateParam); + + expect(clearFlashMessages).toHaveBeenCalled(); + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: parsedQueryParams, + }); + await nextTick(); + expect(setServerPropsSpy).toHaveBeenCalledWith(serverResponse); + }); + + it('redirects when already authorized', async () => { + http.get.mockReturnValue(Promise.resolve(successRedirectResponse)); + OAuthAuthorizeLogic.actions.initializeOAuthPreAuth(entSearchStateParam); + + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: parsedQueryParams, + }); + await nextTick(); + expect(mockLocationReplace).toHaveBeenCalledWith(successRedirectResponse.redirect_uri); + }); + + it('handles error', async () => { + const setHasErrorSpy = jest.spyOn(OAuthAuthorizeLogic.actions, 'setHasError'); + http.get.mockReturnValue(Promise.reject('this is an error')); + + OAuthAuthorizeLogic.actions.initializeOAuthPreAuth(entSearchStateParam); + + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: parsedQueryParams, + }); + await nextTick(); + + expect(setHasErrorSpy).toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts new file mode 100644 index 0000000000000..b63c17538387d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { JSON_HEADER as headers } from '../../../../../common/constants'; +import { clearFlashMessages, flashAPIErrors } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { parseQueryParams } from '../../../shared/query_params'; + +export interface OAuthPreAuthServerProps { + client_id: string; + client_name: string; + redirect_uri: string; + response_type: string; + scope: string; + state: string; + status: string; +} + +export interface OAuthPreAuthorization { + clientId: string; + clientName: string; + redirectUri: string; + responseType: string; + rawScopes: string; + scopes: string[]; + state: string; +} + +interface OAuthAuthorizeValues { + dataLoading: boolean; + buttonLoading: boolean; + cachedPreAuth: OAuthPreAuthorization; + hasError: boolean; +} + +interface OAuthAuthorizeActions { + setServerProps(serverProps: OAuthPreAuthServerProps): OAuthPreAuthServerProps; + initializeOAuthPreAuth(queryString: string): string; + allowOAuthAuthorization(): void; + denyOAuthAuthorization(): void; + setButtonNotLoading(): void; + setHasError(): void; +} + +export const oauthAuthorizeRoute = '/api/workplace_search/oauth/authorize'; + +export const OAuthAuthorizeLogic = kea>({ + path: ['enterprise_search', 'workplace_search', 'oauth_authorize_logic'], + actions: { + setServerProps: (serverProps: OAuthPreAuthServerProps) => serverProps, + initializeOAuthPreAuth: (queryString: string) => queryString, + allowOAuthAuthorization: null, + denyOAuthAuthorization: null, + setButtonNotLoading: null, + setHasError: null, + }, + reducers: { + dataLoading: [ + true, + { + setServerProps: () => false, + setHasError: () => false, + }, + ], + cachedPreAuth: [ + {} as OAuthPreAuthorization, + { + setServerProps: (_, serverProps) => transformServerPreAuth(serverProps), + }, + ], + buttonLoading: [ + false, + { + setButtonNotLoading: () => false, + allowOAuthAuthorization: () => true, + denyOAuthAuthorization: () => true, + }, + ], + hasError: [ + false, + { + setHasError: () => true, + }, + ], + }, + listeners: ({ actions, values }) => ({ + initializeOAuthPreAuth: async (queryString: string) => { + clearFlashMessages(); + const { http } = HttpLogic.values; + const query = parseQueryParams(queryString); + + try { + const response = await http.get(oauthAuthorizeRoute, { query }); + + if (response.status === 'redirect') { + window.location.replace(response.redirect_uri); + } else { + actions.setServerProps(response); + } + } catch (e) { + flashAPIErrors(e); + actions.setHasError(); + } + }, + denyOAuthAuthorization: async () => { + const { http } = HttpLogic.values; + const { cachedPreAuth } = values; + + try { + const response = await http.delete(oauthAuthorizeRoute, { + body: JSON.stringify({ + client_id: cachedPreAuth.clientId, + response_type: cachedPreAuth.responseType, + redirect_uri: cachedPreAuth.redirectUri, + scope: cachedPreAuth.rawScopes, + state: cachedPreAuth.state, + }), + headers, + }); + + window.location.replace(response.redirect_uri); + } catch (e) { + flashAPIErrors(e); + actions.setButtonNotLoading(); + } + }, + allowOAuthAuthorization: async () => { + const { http } = HttpLogic.values; + const { cachedPreAuth } = values; + + try { + const response = await http.post(oauthAuthorizeRoute, { + body: JSON.stringify({ + client_id: cachedPreAuth.clientId, + response_type: cachedPreAuth.responseType, + redirect_uri: cachedPreAuth.redirectUri, + scope: cachedPreAuth.rawScopes, + state: cachedPreAuth.state, + }), + headers, + }); + + window.location.replace(response.redirect_uri); + } catch (e) { + flashAPIErrors(e); + actions.setButtonNotLoading(); + } + }, + }), +}); + +export const transformServerPreAuth = ( + serverProps: OAuthPreAuthServerProps +): OAuthPreAuthorization => ({ + clientId: serverProps.client_id, + clientName: serverProps.client_name, + redirectUri: serverProps.redirect_uri, + responseType: serverProps.response_type, + rawScopes: serverProps.scope, + scopes: serverProps.scope.split(', '), + state: serverProps.state, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/index.ts new file mode 100644 index 0000000000000..966d73ddedb57 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { SearchAuthorize } from './search_authorize'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.test.tsx new file mode 100644 index 0000000000000..a2d8f97e2be39 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '../../../__mocks__/react_router'; +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockValues, setMockActions } from '../../../__mocks__/kea_logic'; + +import React from 'react'; +import { useLocation } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { Loading } from '../../../shared/loading'; + +import { SearchAuthorize } from './search_authorize'; + +describe('SearchAuthorize', () => { + const initializeSearchAuth = jest.fn(); + + const mockValues = { + redirectPending: true, + searchOAuth: { + clientId: 'someUID', + redirectUrl: 'http://localhost:3002/ws/search_callback', + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions({ initializeSearchAuth }); + }); + + it('renders loading and calls initializeSearchAuth', () => { + setMockValues(mockValues); + const search = '?state=someRandomString'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + expect(initializeSearchAuth).toHaveBeenCalled(); + }); + + it('does not call initializeSearchAuth when searchOAuth.clientId is null', () => { + setMockValues({ ...mockValues, searchOAuth: {} }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + expect(initializeSearchAuth).not.toHaveBeenCalled(); + }); + + it('renders flash messages', () => { + setMockValues({ ...mockValues, redirectPending: false }); + const search = '?state=someRandomString'; + (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); + const wrapper = shallow(); + + expect(wrapper.find(FlashMessages)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.tsx new file mode 100644 index 0000000000000..025f052c62f9c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { Location } from 'history'; +import { useActions, useValues } from 'kea'; + +import { EuiPage, EuiPageBody } from '@elastic/eui'; + +import { FlashMessages } from '../../../shared/flash_messages'; +import { Loading } from '../../../shared/loading'; + +import { AppLogic } from '../../app_logic'; + +import { SearchAuthorizeLogic } from './search_authorize_logic'; + +export const SearchAuthorize: React.FC = () => { + const { search } = useLocation() as Location; + const { initializeSearchAuth } = useActions(SearchAuthorizeLogic); + + const { searchOAuth } = useValues(AppLogic); + const { redirectPending } = useValues(SearchAuthorizeLogic); + + useEffect(() => { + if (searchOAuth.clientId) { + initializeSearchAuth(searchOAuth, search); + } + }, [searchOAuth]); + + if (redirectPending) return ; + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts new file mode 100644 index 0000000000000..c14cc094e30a3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, +} from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test/jest'; + +import { SearchAuthorizeLogic } from './search_authorize_logic'; + +describe('SearchAuthorizeLogic', () => { + const { http } = mockHttpValues; + const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers; + const { mount } = new LogicMounter(SearchAuthorizeLogic); + const defaultValues = { + redirectPending: true, + cachedPreAuth: {}, + }; + const entSearchState = 'entSearchStateString'; + const entSearchStateParam = `state=${entSearchState}`; + const searchOAuth = { + clientId: 'someUID', + redirectUrl: 'http://localhost:3002/ws/search_callback', + }; + const preAuthQuery = { + client_id: searchOAuth.clientId, + response_type: 'code', + redirect_uri: searchOAuth.redirectUrl, + scope: 'default_search', + state: entSearchState, + }; + const preAuthServerProps = { + ...preAuthQuery, + client_name: 'Workplace Search', + status: 'Pre-Authorization', + }; + const successRedirectResponse = { + redirect_uri: `${searchOAuth.redirectUrl}?code=authCode&${entSearchStateParam}`, + status: 'redirect', + }; + + beforeEach(() => { + jest.clearAllMocks(); + mount(); + }); + + it('has expected default values', () => { + expect(SearchAuthorizeLogic.values).toEqual(defaultValues); + }); + + describe('listeners', () => { + describe('initializeSearchAuth', () => { + // Mocking window.location.replace(redirectUrl) for redirects + const mockLocationReplace = jest.fn(); + const mockLocation = { + value: { + replace: mockLocationReplace, + }, + writable: true, + }; + Object.defineProperty(window, 'location', mockLocation); + + it('gets pre-authorization and sets values', async () => { + const setServerPropsSpy = jest.spyOn(SearchAuthorizeLogic.actions, 'setServerProps'); + http.get.mockReturnValue(Promise.resolve(preAuthServerProps)); + SearchAuthorizeLogic.actions.initializeSearchAuth(searchOAuth, entSearchStateParam); + + expect(clearFlashMessages).toHaveBeenCalled(); + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: preAuthQuery, + }); + await nextTick(); + expect(setServerPropsSpy).toHaveBeenCalledWith(preAuthServerProps); + }); + + it('redirects when already authorized', async () => { + http.get.mockReturnValue(Promise.resolve(successRedirectResponse)); + SearchAuthorizeLogic.actions.initializeSearchAuth(searchOAuth, entSearchStateParam); + + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: preAuthQuery, + }); + await nextTick(); + expect(mockLocationReplace).toHaveBeenCalledWith(successRedirectResponse.redirect_uri); + }); + + it('handles error', async () => { + const setRedirectNotPendingSpy = jest.spyOn( + SearchAuthorizeLogic.actions, + 'setRedirectNotPending' + ); + http.get.mockReturnValue(Promise.reject('this is an error')); + + SearchAuthorizeLogic.actions.initializeSearchAuth(searchOAuth, entSearchStateParam); + + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/oauth/authorize', { + query: preAuthQuery, + }); + await nextTick(); + + expect(setRedirectNotPendingSpy).toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts new file mode 100644 index 0000000000000..852d0370e00b3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { JSON_HEADER as headers } from '../../../../../common/constants'; +import { SearchOAuth } from '../../../../../common/types'; +import { clearFlashMessages, flashAPIErrors } from '../../../shared/flash_messages'; +import { HttpLogic } from '../../../shared/http'; +import { parseQueryParams } from '../../../shared/query_params'; + +import { + OAuthPreAuthServerProps, + OAuthPreAuthorization, + oauthAuthorizeRoute, + transformServerPreAuth, +} from '../oauth_authorize/oauth_authorize_logic'; + +interface SearchAuthorizeValues { + redirectPending: boolean; + cachedPreAuth: OAuthPreAuthorization; +} + +interface SearchAuthorizeActions { + setServerProps(serverProps: OAuthPreAuthServerProps): OAuthPreAuthServerProps; + initializeSearchAuth(searchOAuth: SearchOAuth, search: string): [SearchOAuth, string]; + authorizeSearch(): void; + setRedirectNotPending(): void; +} + +export const SearchAuthorizeLogic = kea< + MakeLogicType +>({ + path: ['enterprise_search', 'workplace_search', 'search_authorize_logic'], + actions: { + setServerProps: (serverProps: OAuthPreAuthServerProps) => serverProps, + initializeSearchAuth: (searchOAuth: SearchOAuth, search: string) => [searchOAuth, search], + authorizeSearch: null, + setRedirectNotPending: null, + }, + reducers: { + redirectPending: [ + true, + { + setRedirectNotPending: () => false, + }, + ], + cachedPreAuth: [ + {} as OAuthPreAuthorization, + { + setServerProps: (_, serverProps) => transformServerPreAuth(serverProps), + }, + ], + }, + listeners: ({ actions, values }) => ({ + initializeSearchAuth: async ([searchOAuth, search]) => { + clearFlashMessages(); + const { http } = HttpLogic.values; + const { state } = parseQueryParams(search); + + const query = { + client_id: searchOAuth.clientId, + response_type: 'code', + redirect_uri: searchOAuth.redirectUrl, + scope: 'default_search', + state, + }; + + try { + const response = await http.get(oauthAuthorizeRoute, { query }); + + if (response.status === 'redirect') { + window.location.replace(response.redirect_uri); + } else { + actions.setServerProps(response); + } + } catch (e) { + flashAPIErrors(e); + actions.setRedirectNotPending(); + } + }, + setServerProps: () => { + actions.authorizeSearch(); + }, + authorizeSearch: async () => { + const { http } = HttpLogic.values; + const { cachedPreAuth } = values; + + try { + const response = await http.post(oauthAuthorizeRoute, { + body: JSON.stringify({ + client_id: cachedPreAuth.clientId, + response_type: cachedPreAuth.responseType, + redirect_uri: cachedPreAuth.redirectUri, + scope: cachedPreAuth.rawScopes, + state: cachedPreAuth.state, + }), + headers, + }); + + window.location.replace(response.redirect_uri); + } catch (e) { + flashAPIErrors(e); + actions.setRedirectNotPending(); + } + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index d3b04be36978d..a3631a52d696f 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -48,6 +48,10 @@ describe('callEnterpriseSearchConfigAPI', () => { read_only_mode: false, ilm_enabled: true, is_federated_auth: false, + search_oauth: { + client_id: 'someUID', + redirect_url: 'http://localhost:3002/ws/search_callback', + }, configured_limits: { app_search: { engine: { @@ -136,6 +140,10 @@ describe('callEnterpriseSearchConfigAPI', () => { publicUrl: undefined, readOnlyMode: false, ilmEnabled: false, + searchOAuth: { + clientId: undefined, + redirectUrl: undefined, + }, configuredLimits: { appSearch: { engine: { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index 833419e34ef5f..6c1622f54fe81 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -75,6 +75,10 @@ export const callEnterpriseSearchConfigAPI = async ({ publicUrl: stripTrailingSlash(data?.settings?.external_url), readOnlyMode: !!data?.settings?.read_only_mode, ilmEnabled: !!data?.settings?.ilm_enabled, + searchOAuth: { + clientId: data?.settings?.search_oauth?.client_id, + redirectUrl: data?.settings?.search_oauth?.redirect_url, + }, configuredLimits: { appSearch: { engine: { diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts index a21ffac02a48c..aa3b60a5ba047 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/index.ts @@ -8,6 +8,7 @@ import { RouteDependencies } from '../../plugin'; import { registerGroupsRoutes } from './groups'; +import { registerOAuthRoutes } from './oauth'; import { registerOverviewRoute } from './overview'; import { registerRoleMappingsRoutes } from './role_mappings'; import { registerSecurityRoutes } from './security'; @@ -16,6 +17,7 @@ import { registerSourcesRoutes } from './sources'; export const registerWorkplaceSearchRoutes = (dependencies: RouteDependencies) => { registerOverviewRoute(dependencies); + registerOAuthRoutes(dependencies); registerGroupsRoutes(dependencies); registerRoleMappingsRoutes(dependencies); registerSourcesRoutes(dependencies); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts new file mode 100644 index 0000000000000..b239377f6469d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { + registerOAuthAuthorizeRoute, + registerOAuthAuthorizeAcceptRoute, + registerOAuthAuthorizeDenyRoute, +} from './oauth'; + +describe('oauth routes', () => { + describe('GET /api/workplace_search/oauth/authorize', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/oauth/authorize', + }); + + registerOAuthAuthorizeRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + mockRouter.callRoute({}); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/oauth/authorize', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + query: { + access_type: 'offline', + client_id: 'SomeClientUID', + code_challenge: 'SomeRandomString', + code_challenge_method: 'plain', + response_type: 'code', + response_mode: 'query', + redirect_uri: 'https://my.domain/callback', + scope: 'search', + state: 'someRandomString', + }, + }; + mockRouter.shouldValidate(request); + }); + }); + }); + + describe('POST /api/workplace_search/oauth/authorize', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/oauth/authorize', + }); + + registerOAuthAuthorizeAcceptRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + mockRouter.callRoute({}); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/oauth/authorize', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + client_id: 'SomeClientUID', + response_type: 'code', + redirect_uri: 'https://my.domain/callback', + scope: 'search', + state: 'someRandomString', + }, + }; + mockRouter.shouldValidate(request); + }); + }); + }); + + describe('DELETE /api/workplace_search/oauth/authorize', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + + mockRouter = new MockRouter({ + method: 'delete', + path: '/api/workplace_search/oauth/authorize', + }); + + registerOAuthAuthorizeDenyRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + mockRouter.callRoute({}); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/oauth/authorize', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { + body: { + client_id: 'SomeClientUID', + response_type: 'code', + redirect_uri: 'https://my.domain/callback', + scope: 'search', + state: 'someRandomString', + }, + }; + mockRouter.shouldValidate(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts new file mode 100644 index 0000000000000..a94b463499981 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/oauth.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +export function registerOAuthAuthorizeRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/oauth/authorize', + validate: { + query: schema.object({ + access_type: schema.maybe(schema.string()), + client_id: schema.string(), + code_challenge: schema.maybe(schema.string()), + code_challenge_method: schema.maybe(schema.string()), + response_type: schema.string(), + response_mode: schema.maybe(schema.string()), + redirect_uri: schema.maybe(schema.string()), + scope: schema.maybe(schema.string()), + state: schema.maybe(schema.string()), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/oauth/authorize', + }) + ); +} + +export function registerOAuthAuthorizeAcceptRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/api/workplace_search/oauth/authorize', + validate: { + body: schema.object({ + client_id: schema.string(), + response_type: schema.string(), + redirect_uri: schema.maybe(schema.string()), + scope: schema.maybe(schema.string()), + state: schema.maybe(schema.string()), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/oauth/authorize', + }) + ); +} + +export function registerOAuthAuthorizeDenyRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.delete( + { + path: '/api/workplace_search/oauth/authorize', + validate: { + body: schema.object({ + client_id: schema.string(), + response_type: schema.string(), + redirect_uri: schema.maybe(schema.string()), + scope: schema.maybe(schema.string()), + state: schema.maybe(schema.string()), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/oauth/authorize', + }) + ); +} + +export const registerOAuthRoutes = (dependencies: RouteDependencies) => { + registerOAuthAuthorizeRoute(dependencies); + registerOAuthAuthorizeAcceptRoute(dependencies); + registerOAuthAuthorizeDenyRoute(dependencies); +}; From 068aef82bcd127c1a6b11f9e6b463bfb9e0b845a Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 28 Jun 2021 18:45:48 -0400 Subject: [PATCH 28/74] [App Search] Remove analytics tracking from the entire dashboard (#103534) --- .../relevance_tuning_logic.test.ts | 30 ++++++-------- .../relevance_tuning_logic.ts | 2 +- .../sample_response/sample_response_logic.ts | 8 +++- .../components/search/search_logic.test.ts | 6 +-- .../components/search/search_logic.ts | 2 +- .../server/routes/app_search/curations.ts | 17 -------- .../routes/app_search/result_settings.test.ts | 28 ------------- .../routes/app_search/result_settings.ts | 14 ------- .../server/routes/app_search/search.test.ts | 2 +- .../server/routes/app_search/search.ts | 20 ++++----- .../routes/app_search/search_settings.test.ts | 41 ------------------- .../routes/app_search/search_settings.ts | 17 -------- 12 files changed, 32 insertions(+), 155 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 147a3d38add19..4233a7b300d15 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -387,15 +387,12 @@ describe('RelevanceTuningLogic', () => { await nextTick(); expect(RelevanceTuningLogic.actions.setResultsLoading).toHaveBeenCalledWith(true); - expect(http.post).toHaveBeenCalledWith( - '/api/app_search/engines/test-engine/search_settings_search', - { - body: JSON.stringify(searchSettingsWithoutNewBoostProp), - query: { - query: 'foo', - }, - } - ); + expect(http.post).toHaveBeenCalledWith('/api/app_search/engines/test-engine/search', { + body: JSON.stringify(searchSettingsWithoutNewBoostProp), + query: { + query: 'foo', + }, + }); expect(RelevanceTuningLogic.actions.setSearchResults).toHaveBeenCalledWith(searchResults); expect(clearFlashMessages).toHaveBeenCalled(); }); @@ -420,15 +417,12 @@ describe('RelevanceTuningLogic', () => { jest.runAllTimers(); await nextTick(); - expect(http.post).toHaveBeenCalledWith( - '/api/app_search/engines/test-engine/search_settings_search', - { - body: '{}', - query: { - query: 'foo', - }, - } - ); + expect(http.post).toHaveBeenCalledWith('/api/app_search/engines/test-engine/search', { + body: '{}', + query: { + query: 'foo', + }, + }); }); it('will call clearSearchResults if there is no query', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 45f56c1ef36b1..743bb1aa1502b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -268,7 +268,7 @@ export const RelevanceTuningLogic = kea< const { engineName } = EngineLogic.values; const { http } = HttpLogic.values; const { search_fields: searchFields, boosts } = removeBoostStateProps(values.searchSettings); - const url = `/api/app_search/engines/${engineName}/search_settings_search`; + const url = `/api/app_search/engines/${engineName}/search`; actions.setResultsLoading(true); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts index c64cb3465b311..4f0461220e54c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.ts @@ -68,12 +68,16 @@ export const SampleResponseLogic = kea { afterAll(() => jest.useRealTimers()); it('should make a GET API call with a search query', async () => { - http.get.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE)); + http.post.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE)); const logic = mountLogic(); jest.spyOn(logic.actions, 'onSearch'); @@ -90,14 +90,14 @@ describe('SearchLogic', () => { jest.runAllTimers(); await nextTick(); - expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/search', { + expect(http.post).toHaveBeenCalledWith('/api/app_search/engines/some-engine/search', { query: { query: 'hello world' }, }); expect(logic.actions.onSearch).toHaveBeenCalledWith(MOCK_SEARCH_RESPONSE); }); it('handles errors', async () => { - http.get.mockReturnValueOnce(Promise.reject('error')); + http.post.mockReturnValueOnce(Promise.reject('error')); const logic = mountLogic(); logic.actions.search('test'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts index d9b7d575ae0e1..52aedc1659689 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts @@ -61,7 +61,7 @@ export const SearchLogic = kea>({ const { engineName } = EngineLogic.values; try { - const response = await http.get(`/api/app_search/engines/${engineName}/search`, { + const response = await http.post(`/api/app_search/engines/${engineName}/search`, { query: { query }, }); actions.onSearch(response); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts index 4811ceeac408b..3cacab96d1968 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts @@ -115,21 +115,4 @@ export function registerCurationsRoutes({ path: '/as/engines/:engineName/curations/find_or_create', }) ); - - router.get( - { - path: '/api/app_search/engines/{engineName}/curation_search', - validate: { - params: schema.object({ - engineName: schema.string(), - }), - query: schema.object({ - query: schema.string(), - }), - }, - }, - enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v1/engines/:engineName/search.json', - }) - ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts index eab4acd1ea4e1..58f69e4abf968 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts @@ -72,32 +72,4 @@ describe('result settings routes', () => { }); }); }); - - describe('POST /api/app_search/engines/{name}/sample_response_search', () => { - const mockRouter = new MockRouter({ - method: 'post', - path: '/api/app_search/engines/{engineName}/sample_response_search', - }); - - beforeEach(() => { - registerResultSettingsRoutes({ - ...mockDependencies, - router: mockRouter.router, - }); - }); - - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ - params: { engineName: 'some-engine' }, - body: { - query: 'test', - result_fields: resultFields, - }, - }); - - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/:engineName/sample_response_search', - }); - }); - }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts index 9a81b8a9f5907..d028440908173 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts @@ -42,18 +42,4 @@ export function registerResultSettingsRoutes({ path: '/as/engines/:engineName/result_settings', }) ); - - router.post( - skipBodyValidation({ - path: '/api/app_search/engines/{engineName}/sample_response_search', - validate: { - params: schema.object({ - engineName: schema.string(), - }), - }, - }), - enterpriseSearchRequestHandler.createRequest({ - path: '/as/engines/:engineName/sample_response_search', - }) - ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search.test.ts index 9262dd9e574ad..7c81d20334a5a 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search.test.ts @@ -28,7 +28,7 @@ describe('search routes', () => { it('creates a request handler', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v1/engines/:engineName/search.json', + path: '/as/engines/:engineName/dashboard_search', }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts index 216bffc683265..551ad8938abbb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search.ts @@ -22,8 +22,8 @@ export function registerSearchRoutes({ router, enterpriseSearchRequestHandler, }: RouteDependencies) { - router.get( - { + router.post( + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/search', validate: { params: schema.object({ @@ -33,19 +33,15 @@ export function registerSearchRoutes({ query: schema.string(), }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v1/engines/:engineName/search.json', + path: '/as/engines/:engineName/dashboard_search', }) ); - // For the Search UI routes below, Search UI always uses the full API path, like: - // "/api/as/v1/engines/{engineName}/search.json". We only have control over the base path - // in Search UI, so we created a common basepath of "/api/app_search/search-ui" here that - // Search UI can use. - // - // Search UI *also* uses the click tracking and query suggestion endpoints, however, since the - // App Search plugin doesn't use that portion of Search UI, we only set up a proxy for the search endpoint below. + // Search UI always posts it's requests to {some_configured_base_url}/api/as/v1/engines/{engineName}/search.json + // For that reason, we have to create a proxy url with that same suffix below, so that we can proxy Search UI + // requests through Kibana's server. router.post( skipBodyValidation({ path: '/api/app_search/search-ui/api/as/v1/engines/{engineName}/search.json', @@ -56,7 +52,7 @@ export function registerSearchRoutes({ }, }), enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v1/engines/:engineName/search.json', + path: '/as/engines/:engineName/dashboard_search', }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts index 0c11a10ea10f9..7ea533af5da75 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts @@ -132,45 +132,4 @@ describe('search settings routes', () => { }); }); }); - - describe('POST /api/app_search/engines/{name}/search_settings_search', () => { - const mockRouter = new MockRouter({ - method: 'post', - path: '/api/app_search/engines/{engineName}/search_settings_search', - }); - - beforeEach(() => { - registerSearchSettingsRoutes({ - ...mockDependencies, - router: mockRouter.router, - }); - }); - - it('creates a request to enterprise search', () => { - mockRouter.callRoute({ - params: { engineName: 'some-engine' }, - body: searchSettings, - }); - - expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/as/engines/:engineName/search_settings_search', - }); - }); - - describe('validates query', () => { - it('correctly', () => { - const request = { - query: { - query: 'foo', - }, - }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { query: {} }; - mockRouter.shouldThrow(request); - }); - }); - }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts index c8801d6089730..31a8cc09ed839 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts @@ -56,21 +56,4 @@ export function registerSearchSettingsRoutes({ path: '/as/engines/:engineName/search_settings', }) ); - - router.post( - skipBodyValidation({ - path: '/api/app_search/engines/{engineName}/search_settings_search', - validate: { - params: schema.object({ - engineName: schema.string(), - }), - query: schema.object({ - query: schema.string(), - }), - }, - }), - enterpriseSearchRequestHandler.createRequest({ - path: '/as/engines/:engineName/search_settings_search', - }) - ); } From 6feea1a506b86565f63abdd5e344d462ee4b8afb Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 28 Jun 2021 15:56:03 -0700 Subject: [PATCH 29/74] [Enterprise Search] Distinguish between error connecting vs. 5xx responses from Enterprise Search in UI (#103555) * Update Enterprise Search request handler to send back an error connecting header - vs only distinguishing error connecting issues by 502 status + clarify comment where this.handleConnectionError is called - for the most part, auth issues should already be caught by 401s in logic above * Update HttpLogic to set errorConnecting state based on header + update tests etc to match read-only-mode state * [Tech debt] Gracefully handle invalid HTTP responses I've noticed this error a few times after Kibana gets shut down (http.response is undefined) so figured I would catch it here * Fix missing try/catch/flashAPIErrors on engines overview - This is the only http call I found missing a try/catch across our codebase, so we should be set for all views correctly flashing an API error that receive a 5xx response from ent-search Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../enterprise_search/common/constants.ts | 1 + .../components/engines/engines_logic.test.ts | 20 +++++++ .../components/engines/engines_logic.ts | 40 ++++++++----- .../shared/http/http_logic.test.ts | 58 +++++++++++++------ .../applications/shared/http/http_logic.ts | 13 +++-- .../enterprise_search_request_handler.test.ts | 3 +- .../lib/enterprise_search_request_handler.ts | 7 ++- 7 files changed, 100 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 5ccf7c99ba341..ca465aef6d13e 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -80,6 +80,7 @@ export const JSON_HEADER = { Accept: 'application/json', // Required for Enterprise Search APIs }; +export const ERROR_CONNECTING_HEADER = 'x-ent-search-error-connecting'; export const READ_ONLY_MODE_HEADER = 'x-ent-search-read-only-mode'; export const ENTERPRISE_SEARCH_KIBANA_COOKIE = '_enterprise_search'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts index c699f6cf2eb43..d29c8a96f1c60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts @@ -170,6 +170,16 @@ describe('EnginesLogic', () => { }); expect(EnginesLogic.actions.onEnginesLoad).toHaveBeenCalledWith(MOCK_ENGINES_API_RESPONSE); }); + + it('handles errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + EnginesLogic.actions.loadEngines(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); }); describe('loadMetaEngines', () => { @@ -192,6 +202,16 @@ describe('EnginesLogic', () => { MOCK_ENGINES_API_RESPONSE ); }); + + it('handles errors', async () => { + http.get.mockReturnValueOnce(Promise.reject('error')); + mount(); + + EnginesLogic.actions.loadMetaEngines(); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('error'); + }); }); describe('onDeleteEngineSuccess', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts index a33f317d71e52..f9b691147161f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.ts @@ -118,27 +118,35 @@ export const EnginesLogic = kea>({ const { http } = HttpLogic.values; const { enginesMeta } = values; - const response = await http.get('/api/app_search/engines', { - query: { - type: 'indexed', - 'page[current]': enginesMeta.page.current, - 'page[size]': enginesMeta.page.size, - }, - }); - actions.onEnginesLoad(response); + try { + const response = await http.get('/api/app_search/engines', { + query: { + type: 'indexed', + 'page[current]': enginesMeta.page.current, + 'page[size]': enginesMeta.page.size, + }, + }); + actions.onEnginesLoad(response); + } catch (e) { + flashAPIErrors(e); + } }, loadMetaEngines: async () => { const { http } = HttpLogic.values; const { metaEnginesMeta } = values; - const response = await http.get('/api/app_search/engines', { - query: { - type: 'meta', - 'page[current]': metaEnginesMeta.page.current, - 'page[size]': metaEnginesMeta.page.size, - }, - }); - actions.onMetaEnginesLoad(response); + try { + const response = await http.get('/api/app_search/engines', { + query: { + type: 'meta', + 'page[current]': metaEnginesMeta.page.current, + 'page[size]': metaEnginesMeta.page.size, + }, + }); + actions.onMetaEnginesLoad(response); + } catch (e) { + flashAPIErrors(e); + } }, onDeleteEngineSuccess: async ({ engine }) => { flashSuccessToast(DELETE_ENGINE_MESSAGE(engine.name)); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts index a933041c172af..836705e98da94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.test.ts @@ -91,31 +91,42 @@ describe('HttpLogic', () => { jest.spyOn(HttpLogic.actions, 'setErrorConnecting'); }); - it('handles errors connecting to Enterprise Search', async () => { + it('sets errorConnecting to true if the response header is true', async () => { const httpResponse = { - response: { url: '/api/app_search/engines', status: 502 }, + response: { url: '/api/app_search/engines', headers: { get: () => 'true' } }, }; await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); - expect(HttpLogic.actions.setErrorConnecting).toHaveBeenCalled(); + expect(HttpLogic.actions.setErrorConnecting).toHaveBeenCalledWith(true); }); - it('does not handle non-502 Enterprise Search errors', async () => { + it('sets errorConnecting to false if the response header is false', async () => { const httpResponse = { - response: { url: '/api/workplace_search/overview', status: 404 }, + response: { url: '/api/workplace_search/overview', headers: { get: () => 'false' } }, }; await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); - expect(HttpLogic.actions.setErrorConnecting).not.toHaveBeenCalled(); + expect(HttpLogic.actions.setErrorConnecting).toHaveBeenCalledWith(false); }); - it('does not handle errors for non-Enterprise Search API calls', async () => { - const httpResponse = { - response: { url: '/api/some_other_plugin/', status: 502 }, - }; - await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); + describe('isEnterpriseSearchApi check', () => { + let httpResponse: any; + + afterEach(async () => { + // Should always re-reject the promise and not call setErrorConnecting + await expect(interceptedResponse(httpResponse)).rejects.toEqual(httpResponse); + expect(HttpLogic.actions.setErrorConnecting).not.toHaveBeenCalled(); + }); - expect(HttpLogic.actions.setErrorConnecting).not.toHaveBeenCalled(); + it('does not handle non-Enterprise Search API calls', async () => { + httpResponse = { + response: { url: '/api/some_other_plugin/', headers: { get: () => 'true' } }, + }; + }); + + it('does not handle invalid responses', async () => { + httpResponse = {}; + }); }); }); @@ -145,13 +156,24 @@ describe('HttpLogic', () => { expect(HttpLogic.actions.setReadOnlyMode).toHaveBeenCalledWith(false); }); - it('does not handle headers for non-Enterprise Search API calls', async () => { - const httpResponse = { - response: { url: '/api/some_other_plugin/', headers: { get: () => 'true' } }, - }; - await expect(interceptedResponse(httpResponse)).resolves.toEqual(httpResponse); + describe('isEnterpriseSearchApi check', () => { + let httpResponse: any; + + afterEach(async () => { + // Should always resolve the promise and not call setReadOnlyMode + await expect(interceptedResponse(httpResponse)).resolves.toEqual(httpResponse); + expect(HttpLogic.actions.setReadOnlyMode).not.toHaveBeenCalled(); + }); + + it('does not handle non-Enterprise Search API calls', async () => { + httpResponse = { + response: { url: '/api/some_other_plugin/', headers: { get: () => 'true' } }, + }; + }); - expect(HttpLogic.actions.setReadOnlyMode).not.toHaveBeenCalled(); + it('does not handle invalid responses', async () => { + httpResponse = {}; + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts index 18a6db4a281ab..ea3c84c1e0534 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/http/http_logic.ts @@ -9,7 +9,7 @@ import { kea, MakeLogicType } from 'kea'; import { HttpSetup, HttpInterceptorResponseError, HttpResponse } from 'src/core/public'; -import { READ_ONLY_MODE_HEADER } from '../../../../common/constants'; +import { ERROR_CONNECTING_HEADER, READ_ONLY_MODE_HEADER } from '../../../../common/constants'; interface HttpValues { http: HttpSetup; @@ -60,11 +60,12 @@ export const HttpLogic = kea>({ const errorConnectingInterceptor = values.http.intercept({ responseError: async (httpResponse) => { if (isEnterpriseSearchApi(httpResponse)) { - const { status } = httpResponse.response!; - const hasErrorConnecting = status === 502; + const hasErrorConnecting = httpResponse.response!.headers.get(ERROR_CONNECTING_HEADER); - if (hasErrorConnecting) { + if (hasErrorConnecting === 'true') { actions.setErrorConnecting(true); + } else { + actions.setErrorConnecting(false); } } @@ -124,6 +125,8 @@ export const mountHttpLogic = (props: HttpLogicProps) => { * Small helper that checks whether or not an http call is for an Enterprise Search API */ const isEnterpriseSearchApi = (httpResponse: HttpResponse) => { - const { url } = httpResponse.response!; + if (!httpResponse.response) return false; // Typically this means Kibana has stopped working, in which case we short-circuit early to prevent errors + + const { url } = httpResponse.response; return url.includes('/api/app_search/') || url.includes('/api/workplace_search/'); }; diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index 6ebf46abd39d3..e6524151b0a6c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -10,6 +10,7 @@ import { mockConfig, mockLogger, mockHttpAgent } from '../__mocks__'; import { ENTERPRISE_SEARCH_KIBANA_COOKIE, JSON_HEADER, + ERROR_CONNECTING_HEADER, READ_ONLY_MODE_HEADER, } from '../../common/constants'; @@ -380,7 +381,7 @@ describe('EnterpriseSearchRequestHandler', () => { expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, body: 'Error connecting to Enterprise Search: Failed', - headers: mockExpectedResponseHeaders, + headers: { ...mockExpectedResponseHeaders, [ERROR_CONNECTING_HEADER]: 'true' }, }); expect(mockLogger.error).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index 597f7524808e9..b4768c1a9ee15 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -21,6 +21,7 @@ import { ConfigType } from '../'; import { ENTERPRISE_SEARCH_KIBANA_COOKIE, JSON_HEADER, + ERROR_CONNECTING_HEADER, READ_ONLY_MODE_HEADER, } from '../../common/constants'; @@ -144,7 +145,7 @@ export class EnterpriseSearchRequestHandler { body: responseBody, }); } catch (e) { - // Catch connection/auth errors + // Catch connection errors return this.handleConnectionError(response, e); } }; @@ -280,7 +281,9 @@ export class EnterpriseSearchRequestHandler { this.log.error(errorMessage); if (e instanceof Error) this.log.debug(e.stack as string); - return response.customError({ statusCode: 502, headers: this.headers, body: errorMessage }); + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; + + return response.customError({ statusCode: 502, headers, body: errorMessage }); } /** From 6085f90e2db5067b45f7847c45a2903278ec2083 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 28 Jun 2021 18:20:12 -0500 Subject: [PATCH 30/74] [Workplace Search] Port 4 PRs from `ent-search` to `kibana` (#103547) * Poprt #3567 to Kibana https://github.com/elastic/ent-search/pull/3567 * Poer #3582 to Kibana https://github.com/elastic/ent-search/pull/3582 Also adds missing i18n * Port #3634 to Kibana https://github.com/elastic/ent-search/pull/3634 * Port #3758 to Kibana * Rename var --- .../__mocks__/content_sources.mock.ts | 2 + .../components/shared/source_row/constants.ts | 44 +++++++++++++++++++ .../shared/source_row/source_row.test.tsx | 12 ++++- .../shared/source_row/source_row.tsx | 32 ++++++++------ .../applications/workplace_search/types.ts | 1 + .../example_result_detail_card.test.tsx | 16 +++++++ .../example_result_detail_card.tsx | 38 ++++++++++------ .../content_sources/components/overview.tsx | 12 +++-- 8 files changed, 124 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/constants.ts 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 index 30f0dc73eeb02..1d04504cdcbc5 100644 --- 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 @@ -24,6 +24,7 @@ export const contentSources = [ errorReason: null, allowsReauth: true, boost: 1, + activities: [], }, { id: '124', @@ -38,6 +39,7 @@ export const contentSources = [ errorReason: null, allowsReauth: true, boost: 0.5, + activities: [], }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/constants.ts new file mode 100644 index 0000000000000..72dc742e77d8b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/constants.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourceRow.reauthenticateStatusLinkLabel', + { + defaultMessage: 'Re-authenticate', + } +); + +export const SOURCE_ROW_REMOTE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteLabel', + { + defaultMessage: 'Remote', + } +); + +export const SOURCE_ROW_REMOTE_TOOLTIP = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourceRow.remoteTooltip', + { + defaultMessage: + "Remote sources rely on the source's search service directly, and no content is indexed with Workplace Search. Speed and integrity of results are functions of the third-party service's health and performance.", + } +); + +export const SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourceRow.searchableToggleLabel', + { + defaultMessage: 'Source searchable toggle', + } +); + +export const SOURCE_ROW_DETAILS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourceRow.detailsLabel', + { + defaultMessage: 'Details', + } +); 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 index 7e06e0c4aa2f3..b19aa44446f75 100644 --- 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 @@ -35,15 +35,23 @@ describe('SourceRow', () => { expect(onToggle).toHaveBeenCalled(); }); - it('renders "Fix" link', () => { + it('renders "Re-authenticate" link', () => { const source = { ...contentSources[0], status: 'error', errorReason: 'OAuth access token could not be refreshed', + activities: [ + { + status: 'error', + details: [], + event: '', + time: '', + }, + ], }; const wrapper = shallow(); - expect(wrapper.contains('Fix')).toBeTruthy(); + expect(wrapper.contains('Re-authenticate')).toBeTruthy(); }); it('renders loading icon when indexing', () => { 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 index 433e90d75ed64..b40d1a856d0f0 100644 --- 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 @@ -34,6 +34,14 @@ import { SourceIcon } from '../source_icon'; import './source_row.scss'; +import { + SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL, + SOURCE_ROW_REMOTE_LABEL, + SOURCE_ROW_REMOTE_TOOLTIP, + SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL, + SOURCE_ROW_DETAILS_LABEL, +} from './constants'; + // i18n is not needed here because this is only used to check against the server error, which // is not translated by the Kibana team at this time. const CREDENTIALS_REFRESH_NEEDED_PREFIX = 'OAuth access token could not be refreshed'; @@ -61,6 +69,7 @@ export const SourceRow: React.FC = ({ isFederatedSource, errorReason, allowsReauth, + activities, }, onSearchableToggle, isOrganization, @@ -68,32 +77,29 @@ export const SourceRow: React.FC = ({ }) => { const isIndexing = status === statuses.INDEXING; const hasError = status === statuses.ERROR || status === statuses.DISCONNECTED; - const showFix = - isOrganization && + const showReauthenticate = hasError && allowsReauth && - errorReason?.startsWith(CREDENTIALS_REFRESH_NEEDED_PREFIX); + errorReason?.startsWith(CREDENTIALS_REFRESH_NEEDED_PREFIX) && + activities[0]?.status?.toLowerCase() === statuses.ERROR; const rowClass = classNames({ 'source-row--error': hasError }); - const fixLink = ( + const reauthenticateLink = ( - Fix + {SOURCE_ROW_REAUTHENTICATE_STATUS_LINK_LABEL} ); const remoteTooltip = ( <> - Remote - + {SOURCE_ROW_REMOTE_LABEL} + @@ -143,7 +149,7 @@ export const SourceRow: React.FC = ({ onChange={(e: EuiSwitchEvent) => onSearchableToggle(id, e.target.checked)} disabled={!supportedByLicense} compressed - label="Source Searchable Toggle" + label={SOURCE_ROW_SEARCHABLE_TOGGLE_LABEL} showLabel={false} data-test-subj="SourceSearchableToggle" /> @@ -151,7 +157,7 @@ export const SourceRow: React.FC = ({ )} - {showFix && {fixLink}} + {showReauthenticate && {reauthenticateLink}} {showDetails && ( = ({ data-test-subj="SourceDetailsLink" to={getContentSourcePath(SOURCE_DETAILS_PATH, id, !!isOrganization)} > - Details + {SOURCE_ROW_DETAILS_LABEL} )} 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 a653401aade27..bce778f90436c 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 @@ -109,6 +109,7 @@ export interface ContentSourceDetails extends ContentSource { errorReason: string | null; allowsReauth: boolean; boost: number; + activities: SourceActivity[]; } interface DescriptionList { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.test.tsx index 82a421d85df01..5a15ef641be99 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.test.tsx @@ -14,6 +14,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { EuiText } from '@elastic/eui'; + import { ExampleResultDetailCard } from './example_result_detail_card'; describe('ExampleResultDetailCard', () => { @@ -36,4 +38,18 @@ describe('ExampleResultDetailCard', () => { expect(wrapper.find('[data-test-subj="DefaultUrlLabel"]')).toHaveLength(1); }); + + it('shows formatted value when date can be parsed', () => { + const date = '2021-06-28'; + setMockValues({ + ...exampleResult, + searchResultConfig: { detailFields: [{ fieldName: 'date', label: 'Date' }] }, + exampleDocuments: [{ date }], + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiText).children().text()).toContain( + new Date(Date.parse(date)).toLocaleString() + ); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx index 93a7d660215f0..c3d56949d0fe7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx @@ -18,6 +18,11 @@ import { CustomSourceIcon } from './custom_source_icon'; import { DisplaySettingsLogic } from './display_settings_logic'; import { TitleField } from './title_field'; +const getAsLocalDateTimeString = (str: string) => { + const dateValue = Date.parse(str); + return dateValue ? new Date(dateValue).toLocaleString() : null; +}; + export const ExampleResultDetailCard: React.FC = () => { const { sourceName, @@ -60,20 +65,25 @@ export const ExampleResultDetailCard: React.FC = () => {
    {detailFields.length > 0 ? ( - detailFields.map(({ fieldName, label }, index) => ( -
    - -

    {label}

    -
    - -
    {result[fieldName]}
    -
    -
    - )) + detailFields.map(({ fieldName, label }, index) => { + const value = result[fieldName] as string; + const dateValue = getAsLocalDateTimeString(value); + + return ( +
    + +

    {label}

    +
    + +
    {dateValue || value}
    +
    +
    + ); + }) ) : ( )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index cc890e0f104ac..e9b8574032916 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -198,12 +198,16 @@ export const Overview: React.FC = () => { {!custom && ( - {status} {activityDetails && } + + {status} {activityDetails && } + )} - {time} + + {time} + ))} @@ -453,7 +457,7 @@ export const Overview: React.FC = () => { - + @@ -465,7 +469,7 @@ export const Overview: React.FC = () => { )} - + {groups.length > 0 && groupsSummary} {details.length > 0 && {detailsSummary}} From bc097856e61be2dc5285bd11eb22704a8c056647 Mon Sep 17 00:00:00 2001 From: Bhavya RM Date: Mon, 28 Jun 2021 19:33:35 -0400 Subject: [PATCH 31/74] Unskip the reporting screenshots.ts by fixing unable to update UI settings error. (#103184) --- .../functional/apps/dashboard/reporting/screenshots.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts b/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts index 7eb2ef74000e0..881b847f1180b 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/screenshots.ts @@ -29,9 +29,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const ecommerceSOPath = 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json'; - // https://github.com/elastic/kibana/issues/102911 - describe.skip('Dashboard Reporting Screenshots', () => { + describe('Dashboard Reporting Screenshots', () => { before('initialize tests', async () => { + await kibanaServer.uiSettings.replace({ + defaultIndex: '5193f870-d861-11e9-a311-0fa548c5f953', + }); + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load(ecommerceSOPath); await browser.setWindowSize(1600, 850); @@ -215,7 +218,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('downloads a PDF file with saved search given EuiDataGrid enabled', async function () { - await kibanaServer.uiSettings.replace({ 'doc_table:legacy': false }); + await kibanaServer.uiSettings.update({ 'doc_table:legacy': false }); this.timeout(300000); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('Ecom Dashboard'); From 1b5cc2a7bc046f8f9870a6f9f74481df7a7b588b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Mon, 28 Jun 2021 19:43:38 -0400 Subject: [PATCH 32/74] [Security Solution] Disables loadPrebuiltRulesAndTemplatesButton if loading is in progress (#103568) --- .../load_empty_prompt.test.tsx | 22 +++++++++++++++++++ .../pre_packaged_rules/load_empty_prompt.tsx | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index 9e482b228018e..cbdfe5b246aff 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -127,4 +127,26 @@ describe('LoadPrebuiltRulesAndTemplatesButton', () => { ); }); }); + + it('renders disabled button if loading is true', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 0, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 3, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount( + + ); + await waitFor(() => { + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="load-prebuilt-rules"] button').props().disabled + ).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 9a011da9aff05..56875bcc4f88c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -64,12 +64,12 @@ const PrePackagedRulesPromptComponent: React.FC = ( const loadPrebuiltRulesAndTemplatesButton = useMemo( () => getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: !userHasPermissions, + isDisabled: !userHasPermissions || loading, onClick: handlePreBuiltCreation, fill: true, 'data-test-subj': 'load-prebuilt-rules', }), - [getLoadPrebuiltRulesAndTemplatesButton, handlePreBuiltCreation, userHasPermissions] + [getLoadPrebuiltRulesAndTemplatesButton, handlePreBuiltCreation, userHasPermissions, loading] ); return ( From 7442a99168b56a02514519e0ed3e143a60822811 Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 28 Jun 2021 16:44:29 -0700 Subject: [PATCH 33/74] [dev_docs] add tutorial for setting up a development env (#103566) Co-authored-by: Jonathan Budzenski Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../setting_up_a_development_env.mdx | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 dev_docs/tutorials/setting_up_a_development_env.mdx diff --git a/dev_docs/tutorials/setting_up_a_development_env.mdx b/dev_docs/tutorials/setting_up_a_development_env.mdx new file mode 100644 index 0000000000000..449e8b886a44d --- /dev/null +++ b/dev_docs/tutorials/setting_up_a_development_env.mdx @@ -0,0 +1,89 @@ +--- +id: kibDevTutorialSetupDevEnv +slug: /kibana-dev-docs/tutorial/setup-dev-env +title: Setting up a Development Environment +summary: Learn how to setup a development environemnt for contributing to the Kibana repository +date: 2021-04-26 +tags: ['kibana', 'onboarding', 'dev', 'architecture', 'setup'] +--- + +Setting up a development environment is pretty easy. + + + In order to support Windows development we currently require you to use one of the following: + + - [Git Bash](https://git-scm.com/download/win) + - [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about) + + + Before running the steps below, please make sure you have installed [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) and that you are running all commands in either Git Bash or WSL. + + +## Get the code + +Start by forking [the Kibana repository](https://github.com/elastic/kibana) on Github so that you have a place to stage pull requests and create branches for development. + +Then clone the repository to your machine: + +```sh +git clone https://github.com/[YOUR_USERNAME]/kibana.git kibana +cd kibana +``` + +## Install dependencies + +Install the version of Node.js listed in the `.node-version` file. This can be automated with tools such as [nvm](https://github.com/creationix/nvm) or [nvm-windows](https://github.com/coreybutler/nvm-windows). As we also include a `.nvmrc` file you can switch to the correct version when using nvm by running: + +```sh +nvm use +``` + +Then, install the latest version of yarn using: + +```sh +npm install -g yarn +``` + +Finally, boostrap Kibana and install all of the remaining dependencies: + +```sh +yarn kbn bootstrap +``` + +Node.js native modules could be in use and node-gyp is the tool used to build them. There are tools you need to install per platform and python versions you need to be using. Please follow the [node-gyp installation steps](https://github.com/nodejs/node-gyp#installation) for your platform. + +## Run Elasticsearch + +In order to start Kibana you need to run a local version of Elasticsearch. You can startup and initialize the latest Elasticsearch snapshot of the correct version for Kibana by running the following in a new terminal tab/window: + +```sh +yarn es snapshot +``` + +You can pass `--license trial` to start Elasticsearch with a trial license, or use the Kibana UI to switch the local version to a trial version which includes all features. + +Read about more options for [Running Elasticsearch during development](https://www.elastic.co/guide/en/kibana/current/running-elasticsearch.html), like connecting to a remote host, running from source, preserving data inbetween runs, running remote cluster, etc. + +## Run Kibana + +In another terminal tab/window you can start Kibana. + +```sh +yarn start +``` + +If you include the `--run-examples` flag then all of the [developer examples](https://github.com/elastic/kibana/tree/{branch}/examples). Read more about the advanced options for [Running Kibana](https://www.elastic.co/guide/en/kibana/current/running-kibana-advanced.html). + +## Code away! + +You are now ready to start developing. Changes to the source files should be picked up automatically and either cause the server to restart, or be served to the browser on the next page refresh. + +## Install pre-commit hook (optional) + +In case you want to run a couple of checks like linting or check the file casing of the files to commit, we provide a way to install a pre-commit hook. To configure it you just need to run the following: + +```sh +node scripts/register_git_hook +``` + +After the script completes the pre-commit hook will be created within the file `.git/hooks/pre-commit`. If you choose to not install it, don’t worry, we still run a quick CI check to provide feedback earliest as we can about the same checks. From d7d4a14c8d0d51a6085afd8a30b6e192650d2887 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 28 Jun 2021 18:11:10 -0600 Subject: [PATCH 34/74] [Security Solutions][Detection Engine] Implements best effort merging of constant_keyword, runtime fields, aliases, and copy_to fields (#102280) ## Summary This adds utilities and two strategies for merging using the [fields API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html) and the `_source` document during signal generation. This gives us the ability to support `constant_keyword`, field alias value support, some runtime fields support, and `copy_to` support. Previously we did not copy any of these values and only generated signals based on the `_source` record values. This changes the behavior to allow us to copy some of the mentioned values above. The folder of `source_fields_merging` contains a `strategy` folder and a `utils` folder which contains both the strategies and the utilities for this implementation. The two strategies are `merge_all_fields_with_source` and `merge_missing_fields_with_source`. The defaulted choice for this PR is we use `merge_missing_fields_with_source` and not the `merge_all_fields_with_source`. The reasoning is that this is much lower risk and lower behavior changes to the signals detection engine. The main driving force behind this PR is that ECS has introduced `constant_keyword` and that field has the possibility of only showing up in the fields section of a document and not `_source` when index authors do not push the `constant_keyword` into the `_source` section. The secondary driving forces behind this behavioral change is that some users have been expecting their runtime fields, `copy_to` fields, and field alias values of their indexes to be copied into the signals index. Both strategies of `merge_missing_fields_with_source` and `merge_all_fields_with_source` are considered Best Effort meaning that both strategies will not always merge as expected when they encounter ambiguous use cases as outlined in the `README.md` text at the top of `source_fields_merging` in detail. The default used strategy of `merge_missing_fields_with_source` which has the simplest behavior will work in most common use cases. This is simply if the `_source` document is missing a value that is present in the `fields`, and the `fields` value is a primitive concrete value such as a `string` or `number` or `boolean` and the `_source` document does not contain an existing object or ambiguous array, then the value will be merged into `_source` and a new reference is returned. If you call the strategy twice it should be idempotent meaning that the second call will detect a value is now present in `_source` and not re-merge a second time. * 301 unit tests were added * Extensive README.md docs are added * e2e tests are updated to test scenarios and ambiguity and conflicts from previously to support this effort. * Other e2e tests were updated * One bug with EQL and fields was found with a workaround implemented. See https://github.com/elastic/elasticsearch/issues/74582 * SearchTypes adjusted to use recursive TypeScript types * Changed deprecated for `@deprecated` in a few spots * Removed some `ts-expect-error` in favor of `??` in a few areas * Added a new handling of epoch strings and tests to `detection_engine/signals/utils.ts` since fields returns `epoch_millis` as a string instead of as a number. * Uses lodash safer set to reduce changes of prototype pollution ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### Risk Matrix | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Prototype pollution | Low | High | Used lodash safer set | | Users which have existing rules that work, upgrade and now we do not generate signals due to bad merging of fields and _source | Mid | High | We use the safer strategy method, `merge_missing_fields_with_source `, that is lighter weight to start with. We might add a follow up PR which enables a key in Kibana to turn off merging of fields with source. We added extensive unit tests and e2e tests. However, unexpected unknowns and behaviors from runtime fields and fields API such as geo-points looking like nested fields or `epoch_milliseconds` being a string value or runtime fields allowing invalid values were uncovered and tests and utilities around that have been added which makes this PR risky | | Found a bug with using fields and EQL which caused EQL rules to not run. | Low | High | Implemented workaround for tests to pass and created an Elastic ticket and communicated the bug to EQL developers. | --- .../detection_engine/get_query_filter.test.ts | 30 + .../detection_engine/get_query_filter.ts | 14 + .../common/detection_engine/types.ts | 18 +- .../__mocks__/empty_signal_source_hit.ts | 17 + .../signals/build_bulk_body.test.ts | 24 + .../signals/build_bulk_body.ts | 34 +- .../signals/source_fields_merging/README.md | 389 +++++ .../signals/source_fields_merging/index.ts | 9 + .../source_fields_merging/strategies/index.ts | 8 + .../merge_all_fields_with_source.test.ts | 1478 +++++++++++++++++ .../merge_all_fields_with_source.ts | 113 ++ .../merge_missing_fields_with_source.test.ts | 1379 +++++++++++++++ .../merge_missing_fields_with_source.ts | 88 + .../signals/source_fields_merging/types.ts | 11 + .../utils/array_in_path_exists.test.ts | 42 + .../utils/array_in_path_exists.ts | 23 + .../utils/filter_field_entries.test.ts | 83 + .../utils/filter_field_entries.ts | 32 + .../source_fields_merging/utils/index.ts | 16 + .../utils/is_array_of_primitives.test.ts | 51 + .../utils/is_array_of_primitives.ts | 21 + .../utils/is_invalid_key.test.ts | 51 + .../utils/is_invalid_key.ts | 16 + .../utils/is_multifield.test.ts | 40 + .../utils/is_multifield.ts | 34 + .../utils/is_nested_object.test.ts | 46 + .../utils/is_nested_object.ts | 22 + ...objectlike_or_array_of_objectlikes.test.ts | 71 + .../is_objectlike_or_array_of_objectlikes.ts | 25 + .../utils/is_primitive.test.ts | 42 + .../utils/is_primitive.ts | 16 + .../utils/is_type_object.test.ts | 34 + .../utils/is_type_object.ts | 25 + .../utils/recursive_unboxing_fields.test.ts | 292 ++++ .../utils/recursive_unboxing_fields.ts | 60 + .../lib/detection_engine/signals/types.ts | 27 +- .../detection_engine/signals/utils.test.ts | 39 + .../lib/detection_engine/signals/utils.ts | 9 + .../security_and_spaces/tests/aliases.ts | 7 +- .../security_and_spaces/tests/create_ml.ts | 7 + .../tests/keyword_family/const_keyword.ts | 6 +- .../keyword_mixed_with_const.ts | 6 +- .../security_and_spaces/tests/runtime.ts | 67 +- .../security_solution/alias/data.json | 8 +- .../runtime_conflicting_fields/mappings.json | 5 +- 45 files changed, 4773 insertions(+), 62 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/empty_signal_source_hit.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/types.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts index 63a38ad7d71c1..7de082e778a07 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.test.ts @@ -1143,6 +1143,16 @@ describe('get_filter', () => { ], }, }, + fields: [ + { + field: '*', + include_unmapped: true, + }, + { + field: '@timestamp', + format: 'epoch_millis', + }, + ], }, }); }); @@ -1180,6 +1190,16 @@ describe('get_filter', () => { ], }, }, + fields: [ + { + field: '*', + include_unmapped: true, + }, + { + field: '@timestamp', + format: 'epoch_millis', + }, + ], }, }); }); @@ -1262,6 +1282,16 @@ describe('get_filter', () => { ], }, }, + fields: [ + { + field: '*', + include_unmapped: true, + }, + { + field: '@timestamp', + format: 'epoch_millis', + }, + ], }, }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts index 6a61f892747b4..86e66577abd45 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/get_query_filter.ts @@ -121,6 +121,20 @@ export const buildEqlSearchRequest = ( }, }, event_category_field: eventCategoryOverride, + fields: [ + { + field: '*', + include_unmapped: true, + }, + { + field: '@timestamp', + // BUG: We have to format @timestamp until this bug is fixed with epoch_millis + // https://github.com/elastic/elasticsearch/issues/74582 + // TODO: Remove epoch and use the same techniques from x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts + // where we format both the timestamp and any overrides as ISO8601 + format: 'epoch_millis', + }, + ], }, }; }; 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 c0e502312b2ff..e7b8cca8d5a97 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -11,16 +11,14 @@ export type RuleAlertAction = Omit & { action_type_id: string; }; -export type SearchTypes = - | string - | string[] - | number - | number[] - | boolean - | boolean[] - | object - | object[] - | undefined; +/** + * Defines the search types you can have from Elasticsearch within a + * doc._source. It uses recursive types of "| SearchTypes[]" to designate + * anything can also be of a type array, and it uses the recursive type of + * "| { [property: string]: SearchTypes }" to designate you can can sub-objects + * or sub-sub-objects, etc... + */ +export type SearchTypes = string | number | boolean | object | SearchTypes[] | undefined; export interface Explanation { value: number; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/empty_signal_source_hit.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/empty_signal_source_hit.ts new file mode 100644 index 0000000000000..805a401f782fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/empty_signal_source_hit.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SignalSourceHit } from '../types'; + +/** + * Simple empty Elasticsearch result for testing + * @returns Empty Elasticsearch result for testing + */ +export const emptyEsResult = (): SignalSourceHit => ({ + _index: 'index', + _id: '123', +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index 4d3ca26f5a71e..4053d64539c49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -77,6 +77,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -160,6 +163,9 @@ describe('buildBulkBody', () => { }, depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -222,6 +228,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -282,6 +291,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -335,6 +347,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -388,6 +403,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -441,6 +459,9 @@ describe('buildBulkBody', () => { rule: expectedRule(), depth: 1, }, + source: { + ip: '127.0.0.1', + }, }; expect(fakeSignalSourceHit).toEqual(expected); }); @@ -673,6 +694,9 @@ describe('buildSignalFromEvent', () => { rule: expectedRule(), depth: 2, }, + source: { + ip: '127.0.0.1', + }, }; expect(signal).toEqual(expected); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts index 10cc168700447..819e1f3eb6df1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts @@ -6,6 +6,7 @@ */ import { SavedObject } from 'src/core/types'; +import { mergeMissingFieldsWithSource } from './source_fields_merging/strategies/merge_missing_fields_with_source'; import { AlertAttributes, SignalSourceHit, @@ -21,18 +22,27 @@ import { buildEventTypeSignal } from './build_event_type_signal'; import { EqlSequence } from '../../../../common/detection_engine/types'; import { generateSignalId, wrapBuildingBlocks, wrapSignal } from './utils'; -// format search_after result for signals index. +/** + * Formats the search_after result for insertion into the signals index. We first create a + * "best effort" merged "fields" with the "_source" object, then build the signal object, + * then the event object, and finally we strip away any additional temporary data that was added + * such as the "threshold_result". + * @param ruleSO The rule saved object to build overrides + * @param doc The SignalSourceHit with "_source", "fields", and additional data such as "threshold_result" + * @returns The body that can be added to a bulk call for inserting the signal. + */ export const buildBulkBody = ( ruleSO: SavedObject, doc: SignalSourceHit ): SignalHit => { - const rule = buildRuleWithOverrides(ruleSO, doc._source!); + const mergedDoc = mergeMissingFieldsWithSource({ doc }); + const rule = buildRuleWithOverrides(ruleSO, mergedDoc._source ?? {}); const signal: Signal = { - ...buildSignal([doc], rule), - ...additionalSignalFields(doc), + ...buildSignal([mergedDoc], rule), + ...additionalSignalFields(mergedDoc), }; - const event = buildEventTypeSignal(doc); - const { threshold_result: thresholdResult, ...filteredSource } = doc._source || { + const event = buildEventTypeSignal(mergedDoc); + const { threshold_result: thresholdResult, ...filteredSource } = mergedDoc._source || { threshold_result: null, }; const signalHit: SignalHit = { @@ -122,18 +132,18 @@ export const buildSignalFromEvent = ( ruleSO: SavedObject, applyOverrides: boolean ): SignalHit => { + const mergedEvent = mergeMissingFieldsWithSource({ doc: event }); const rule = applyOverrides - ? // @ts-expect-error @elastic/elasticsearch _source is optional - buildRuleWithOverrides(ruleSO, event._source) + ? buildRuleWithOverrides(ruleSO, mergedEvent._source ?? {}) : buildRuleWithoutOverrides(ruleSO); const signal: Signal = { - ...buildSignal([event], rule), - ...additionalSignalFields(event), + ...buildSignal([mergedEvent], rule), + ...additionalSignalFields(mergedEvent), }; - const eventFields = buildEventTypeSignal(event); + const eventFields = buildEventTypeSignal(mergedEvent); // TODO: better naming for SignalHit - it's really a new signal to be inserted const signalHit: SignalHit = { - ...event._source, + ...mergedEvent._source, '@timestamp': new Date().toISOString(), event: eventFields, signal, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md new file mode 100644 index 0000000000000..eb72fc2b32687 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md @@ -0,0 +1,389 @@ +Set of utilities for merging between `_source` and `fields` are within this folder as well as strategies for merging between these two. + +See `strategies` for the strategies for merging between `_source` and `fields`. See the `utils` folder for the different utilities +which the strategies utilize for help in building their merged documents. + +If we run into problems such as ambiguities, uncertainties, or data type contradictions then we will prefer the value within +"doc.fields" when we can. If "doc.fields" contradicts its self or is too ambiguous, then we assume that +there a problem within "doc.fields" due to a malformed runtime field definition and omit the last seen +contradiction. In some cases we might have to omit the merging of the field altogether and instead utilize +the value from "doc._source" + +Hence, these are labeled as "best effort" since we could run into conditions where we should have taken the value +from "doc.fields" but instead did not and took the value from "doc._source". + +If "doc.fields" does not exist we return "doc._source" untouched as-is. If "doc._source" does not exist but +"doc.fields" does exist then we will do a "best effort" to merge "doc.fields" into a fully functional object as +if it was a "doc._source". But again, if we run into contradictions or ambiguities from the +"doc.fields" we will remove that field or omit one of the contradictions. + +If a "doc.field" is found that does not exist in "doc._source" then we merge that "doc.field" into our +return object. + +If we find that a "field" contradicts the "doc._source" object in which we cannot create a regular +JSON such as a keyword trying to override an object or an object trying to override a keyword: + +``` +"fields": { 'foo': 'value_1', foo.bar': 'value_2' } <-- Foo cannot be both an object and a value +``` +Then you will get an object such as + +``` +{ "foo": "value_1" } +``` + +We cannot merge both together as this is a contradiction and no longer capable of being a JSON object. +This happens when we have multiFields since multiFields are represented in fields as well as when runtime +fields tries to add multiple overrides or invalid multiFields. + +Invalid field names such as ".", "..", ".foo", "foo.", ".foo." will be skipped as those cause errors if +we tried to insert them into Elasticsearch as a new field. + +If we encounter an array within "doc._source" that contains an object with more than 1 value and a "field" +tries to add a new element we will not merge that in as we do not know which array to merge that value into. + +If we encounter a flattened array in the fields object which is not a nested fields such as: +``` +"fields": { "object_name.first" : [ "foo", "bar" ], "object_name.second" : [ "mars", "bar" ] } +``` + +and no "doc._source" with the name "object_name", the assumption is that we these are not related and we construct the object as this: + +``` +{ "object.name": { "first": ["foo", "bar" }, "second": ["mars", "bar"] } +``` + +If we detect a "doc._source with a single flattened array sub objects we will prefer the "fields" flattened +array elements and copy them over as-is, which means we could be subtracting elements, adding elements, or +completely changing the items from the array. + +If we detect an object within the "doc._source" inside of the array, we will not take anything from the +"fields" flattened array elements even if they exist as it is ambiguous where we would put those elements +within the ""doc._source" as an override. + +It is best to feed this both the "doc._source" and "doc.fields" values to get the best chances of merging the document correctly. + +Using these strategies will get you these value types merged that you would otherwise not get directly on your +``` +"doc._source": + - constant_keyword field + - runtime fields + - field aliases + - copy_to +``` + +References: +--- + * https://www.elastic.co/guide/en/elasticsearch/reference/7.13/keyword.html#constant-keyword-field-type + * https://www.elastic.co/guide/en/elasticsearch/reference/7.13/runtime.html + * https://www.elastic.co/guide/en/elasticsearch/reference/7.13/search-fields.html + +Ambiguities and issues +--- +* geo data points/types and nested fields look the same. +* multi-fields such as `host.name` and `host.name.keyword` can lead to misinterpreting valid values vs multi-fields +* All data is an array with at least 1 value we call "boxed", meaning that it is difficult to determine if the user wanted the fields as an array or not. + +Existing bugs and ambiguities +--- +* We currently filter out the geo data points by looking at "type" on the object and filter it out. We could transform it to be valid input at some point. + +Tests +--- +Some tests in this folder use a special table and nomenclature in the comments to show the enumerations and tests for each type. + +Key for the nomenclature is: +``` +undefined means non-existent +p_[] means primitive key and empty array +p_p1 or p_p2 means primitive key and primitive value +p_[p1] or p_[p2] means primitive key and primitive array with a single array value +p[p1, ...1] or p[p2, ...2] means primitive array with 2 or more values +p_{}1 or p_{}2 means a primitive key with a single object +p_[{}1] or p_[{}2] means a primitive key with an array of exactly 1 object +p_[{}1, ...1] or p_[{}2, ...2] means a primitive key with 2 or more array elements +f_[] means a flattened object key and empty array +f_p1 or f_p2 means a flattened object key and a primitive value +f_[p1] or f_[p2] means a flattened object key and a single primitive value in an array +f_[p1, ...1] or f_[p2, ...2] means a flattened object key and 2 or more primitive values in an array +f_{}1 or f_{}2 means a flattened object key with 1 object +f_[{}1] or f_[{}2] means a flattened object key with a single object in a single array +f_[{}1, ...1] or f_[{}2, ...2] means a flattened object key with 2 or more objects in an array +``` + +`_source` documents can contain the following values: +``` +undefined +p_[] +p_p1 +p_[p1] +p_[p1, ...1] +p_{}1 +p_[{}1] +p_[{}1, ...1] +f_[] +f_p1 +f_[p1] +f_[p1, ...1] +f_{}1 +f_[{}1] +f_[{}1, ...1] +``` + +fields arrays can contain the following values: +``` +undefined +f_[] +f_[p2] +f_[p2, ...2] +f_[{}2] +f_[{}2, ...2] +``` + +When fields is undefined or empty array f_[] you never overwrite +the source and source is always the same as before the merge for all the strategies +``` +source | fields | value after merge +----- | --------- | ----- +undefined | undefined | undefined +undefined | f_[] | undefined +p_[] | undefined | p_[] +p_[] | f_[] | p_[] +p_p1 | undefined | p_p1 +p_p1 | f_[] | p_p1 +p_[p1] | undefined | p_[p1] +p_[p1] | f_[] | p_[p1] +p_[p1, ...1] | undefined | p_[p1, ...1] +p_[p1, ...1] | f_[] | p_[p1, ...1] +p_{}1 | undefined | p_{}1 +p_{}1 | f_[] | p_{}1 +p_[{}1] | undefined | p_{}1 +p_[{}1] | f_[] | p_{}1 +p_[{}1, ...1] | undefined | p_[{}1, ...1] +p_[{}1, ...1] | f_[] | p_[{}1, ...1] +f_[] | undefined | f_[] +f_[] | f_[] | f_[] +f_p1 | undefined | f_p1 +f_p1 | f_[] | f_p1 +f_[p1] | undefined | f_[p1] +f_[p1] | f_[] | f_[p1] +f_[p1, ...1] | undefined | f_[p1, ...1] +f_[p1, ...1] | f_[] | f_[p1, ...1] +f_{}1 | undefined | f_{}1 +f_{}1 | f_[] | f_{}1 +f_[{}1] | undefined | f_{}1 +f_[{}1] | f_[] | f_{}1 +f_[{}1, ...1] | undefined | f_[{}1, ...1] +f_[{}1, ...1] | f_[] | f_[{}1, ...1] +``` + +When source key and source value does not exist but field keys and values do exist, then you +you will always get field keys and values replacing the source key and value. Caveat is that +fields will create a single item rather than an array item if field keys and value only has a single +array element. Also, we prefer to create an object structure in source (e.x. p_p2 instead of a flattened object f_p2) +for the `merge_all_fields_with_source` strategy +``` +source | fields | value after merge +----- | --------- | ----- +undefined | f_[p2] | p_p2 <-- Unboxed from array +undefined | f_[p2, ...2] | p_[p2, ...2] +undefined | f_[{}2] | p_{}2 <-- Unboxed from array +undefined | f_[{}2, ...2] | p_[{}2, ...2] +``` + +For the `merge_missing_fields_with_source` it will be that we completely skip the fields that contain nested +fields or type fields such as geo points. + +``` +source | fields | value after merge +----- | --------- | ----- +undefined | f_[p2] | p_p2 <-- Unboxed from array +undefined | f_[p2, ...2] | p_[p2, ...2] +undefined | f_[{}2] | {} <-- We have an empty object since we only merge primitives +undefined | f_[{}2, ...2] | {} <-- We have an empty object since we only merge primitives +``` + +When source key is either a primitive key or a flattened object key with a primitive value (p_p1 or f_p1), +then we overwrite source value with fields value as an unboxed value array if fields value is a +single array element (f_[p2] or f[{}2]), otherwise we overwrite source as an array. + +``` +source | fields | value after merge +----- | --------- | ----- +p_p1 | f_[p2] | p_p2 <-- Unboxed from array +p_p1 | f_[p2, ...2] | p_[p2, ...2] +p_p1 | f_[{}2] | p_{}2 <-- Unboxed from array +p_p1 | f_[{}2, ...2] | p_[{}2, ...2] + +f_p1 | f_[p2] | f_p2 <-- Unboxed from array +f_p1 | f_[p2, ...2] | f_[p2, ...2] +f_p1 | f_[{}2] | f_{}2 <-- Unboxed from array +f_p1 | f_[{}2, ...2] | f_[{}2, ...2] +``` + +For the `merge_missing_fields_with_source` none of these will be merged since the source has values such as + +``` +source | fields | value after merge +----- | --------- | ----- +p_p1 | f_[p2] | p_p1 +p_p1 | f_[p2, ...2] | p_p1 +p_p1 | f_[{}2] | p_p1 +p_p1 | f_[{}2, ...2] | p_p1 + +f_p1 | f_[p2] | f_p1 +f_p1 | f_[p2, ...2] | f_p1 +f_p1 | f_[{}2] | f_p1 +f_p1 | f_[{}2, ...2] | f_p1 +``` + +When source key is a primitive key or a flattened object key and the source value is any +type of array (p_[], p_p[p1], or p_p[p1, ...1]) of primitives then we always copy the +fields value as is and keep the source key as it was originally (primitive or flattened) + +``` +source | fields | value after merge +----- | --------- | ----- +p_[] | f_[p2] | p_[p2] +p_[] | f_[p2, ...2] | p_[p2, ...2] +p_[] | f_[{}2] | p_[{}2] +p_[] | f_[{}2, ...2] | p_[{}2, ...2] + +f_[] | f_[p2] | f_[p2] +f_[] | f_[p2, ...2] | f_[p2, ...2] +f_[] | f_[{}2] | f_[{}2] +f_[] | f_[{}2, ...2] | f_[{}2, ...2] + +p_[p1] | f_[p2] | p_[p2] +p_[p1] | f_[p2, ...2] | p_[p2, ...2] +p_[p1] | f_[{}2] | p_[{}2] +p_[p1] | f_[{}2, ...2] | p_[{}2, ...2] + +f_[p1] | f_[p2] | f_[p2] +f_[p1] | f_[p2, ...2] | f_[p2, ...2] +f_[p1] | f_[{}2] | f_{}2 +f_[p1] | f_[{}2, ...2] | f_[{}2, ...2] + +p_[p1, ...1] | f_[p2] | p_[p2] +p_[p1, ...1] | f_[p2, ...2] | p_[p2, ...2] +p_[p1, ...1] | f_[{}2] | p_[{}2] +p_[p1, ...1] | f_[{}2, ...2] | p_[{}2, ...2] + +f_[p1, ...1] | f_[p2] | f_[p2] +f_[p1, ...1] | f_[p2, ...2] | f_[p2, ...2] +f_[p1, ...1] | f_[{}2] | f_[{}2] +f_[p1, ...1] | f_[{}2, ...2] | f_[{}2, ...2] +``` + +For the `merge_missing_fields_with_source` none of these will be merged since the source has values such as + +``` +source | fields | value after merge +----- | --------- | ----- +p_[] | f_[p2] | p_[] +p_[] | f_[p2, ...2] | p_[] +p_[] | f_[{}2] | p_[] +p_[] | f_[{}2, ...2] | p_[] + +f_[] | f_[p2] | f_[] +f_[] | f_[p2, ...2] | f_[] +f_[] | f_[{}2] | f_[] +f_[] | f_[{}2, ...2] | f_[] + +p_[p1] | f_[p2] | p_[p1] +p_[p1] | f_[p2, ...2] | p_[p1] +p_[p1] | f_[{}2] | p_[p1] +p_[p1] | f_[{}2, ...2] | p_[p1] + +f_[p1] | f_[p2] | f_[p1] +f_[p1] | f_[p2, ...2] | f_[p1] +f_[p1] | f_[{}2] | f_[p1] +f_[p1] | f_[{}2, ...2] | f_[p1] + +p_[p1, ...1] | f_[p2] | p_[p1, ...1] +p_[p1, ...1] | f_[p2, ...2] | p_[p1, ...1] +p_[p1, ...1] | f_[{}2] | p_[p1, ...1] +p_[p1, ...1] | f_[{}2, ...2] | p_[p1, ...1] + +f_[p1, ...1] | f_[p2] | f_[p1, ...1] +f_[p1, ...1] | f_[p2, ...2] | f_[p1, ...1] +f_[p1, ...1] | f_[{}2] | f_[p1, ...1] +f_[p1, ...1] | f_[{}2, ...2] | f_[p1, ...1] +``` + +When source key is a primitive key or flattened key and the source value is an object (p_{}1, f_{}1) or +an array containing objects ([p_{1}], f_{}1, p_[{}1, ...1], f_[{}1, ...1]), we only copy the +field value if we detect that field value is also an object meaning that it is a nested field, +(f_[{}]2 or f[{}2, ...2]). We never allow a field to convert an object back into a value. +We never try to merge field values into the array either since they're flattened in the fields and we +will have too many ambiguities and issues between the flattened array values and the source objects. + +``` +source | fields | value after merge +----- | --------- | ----- +p_{}1 | f_[p2] | p_{}1 +p_{}1 | f_[p2, ...2] | p_{}1 +p_{}1 | f_[{}2] | p_{}2 <-- Copied and unboxed array since we detected a nested field +p_{}1 | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + +f_{}1 | f_[p2] | f_{}1 +f_{}1 | f_[p2, ...2] | f_{}1 +f_{}1 | f_[{}2] | f_{}2 <-- Copied and unboxed array since we detected a nested field +f_{}1 | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field + +p_[{}1] | f_[p2] | p_[{}1] +p_[{}1] | f_[p2, ...2] | p_[{}1] +p_[{}1] | f_[{}2] | p_[{}2] <-- Copied since we detected a nested field +p_[{}1] | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + +f_[{}1] | f_[p2] | f_[{}1] +f_[{}1] | f_[p2, ...2] | f_[{}1] +f_[{}1] | f_[{}2] | f_[{}2] <-- Copied since we detected a nested field +f_[{}1] | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field + +p_[{}1, ...1] | f_[p2] | p_[{}1, ...1] +p_[{}1, ...1] | f_[p2, ...2] | p_[{}1, ...1] +p_[{}1, ...1] | f_[{}2] | p_[{}2] <-- Copied since we detected a nested field +p_[{}1, ...1] | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + +f_[{}1, ...1] | f_[p2] | f_[{}1, ...1] +f_[{}1, ...1] | f_[p2, ...2] | f_[{}1, ...1] +f_[{}1, ...1] | f_[{}2] | f_[{}2] <-- Copied since we detected a nested field +f_[{}1, ...1] | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field +``` + +For the `merge_missing_fields_with_source` none of these will be merged since the source has values such as + +``` +source | fields | value after merge +----- | --------- | ----- +p_{}1 | f_[p2] | p_{}1 +p_{}1 | f_[p2, ...2] | p_{}1 +p_{}1 | f_[{}2] | p_{}1 +p_{}1 | f_[{}2, ...2] | p_{}1 + +f_{}1 | f_[p2] | f_{}1 +f_{}1 | f_[p2, ...2] | f_{}1 +f_{}1 | f_[{}2] | f_{}1 +f_{}1 | f_[{}2, ...2] | f_{}1 + +p_[{}1] | f_[p2] | p_[{}1] +p_[{}1] | f_[p2, ...2] | p_[{}1] +p_[{}1] | f_[{}2] | p_[{}1] +p_[{}1] | f_[{}2, ...2] | p_[{}1] + +f_[{}1] | f_[p2] | f_[{}1] +f_[{}1] | f_[p2, ...2] | f_[{}1] +f_[{}1] | f_[{}2] | f_[{}1] +f_[{}1] | f_[{}2, ...2] | f_[{}1] + +p_[{}1, ...1] | f_[p2] | p_[{}1, ...1] +p_[{}1, ...1] | f_[p2, ...2] | p_[{}1, ...1] +p_[{}1, ...1] | f_[{}2] | p_[{}1, ...1] +p_[{}1, ...1] | f_[{}2, ...2] | p_[{}1, ...1] + +f_[{}1, ...1] | f_[p2] | f_[{}1, ...1] +f_[{}1, ...1] | f_[p2, ...2] | f_[{}1, ...1] +f_[{}1, ...1] | f_[{}2] | f_[{}1, ...1] +f_[{}1, ...1] | f_[{}2, ...2] | f_[{}1, ...1] +``` diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/index.ts new file mode 100644 index 0000000000000..ff07c898a3a24 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './types'; +export * from './strategies'; +export * from './utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/index.ts new file mode 100644 index 0000000000000..212eba9c6c3be --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './merge_all_fields_with_source'; +export * from './merge_missing_fields_with_source'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.test.ts new file mode 100644 index 0000000000000..b900ea268fd6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.test.ts @@ -0,0 +1,1478 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mergeAllFieldsWithSource } from './merge_all_fields_with_source'; +import { SignalSourceHit } from '../../types'; +import { emptyEsResult } from '../../__mocks__/empty_signal_source_hit'; + +/** + * See ../README.md for the nomenclature of any notes within tests below + */ +describe('merge_all_fields_with_source', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + /** Get the return type of the mergeAllFieldsWithSource for TypeScript checks against expected */ + type ReturnTypeMergeFieldsWithSource = ReturnType['_source']; + + describe('fields is "undefined"', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | undefined | undefined + * p_[] | undefined | p_[] + * p_p1 | undefined | p_p1 + * p_[p1] | undefined | p_[p1] + * p_[p1, ...1] | undefined | p_[p1, ...1] + * p_{}1 | undefined | p_{}1 + * p_[{}1] | undefined | p_{}1 + * p_[{}1, ...1] | undefined | p_[{}1, ...1] + */ + describe('primitive keys in the _source document', () => { + /** fields is "undefined" for all tests below */ + const fields: SignalSourceHit['fields'] = {}; + + test('when source is "undefined", merged doc is "undefined"', () => { + const _source: SignalSourceHit['_source'] = {}; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an empty array (p_[]), merged doc is empty array (p_[])"', () => { + const _source: SignalSourceHit['_source'] = { + foo: [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (p_p1), merged doc is primitive (p_p1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (p_[p1]), merged doc is primitive (p_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (p_[p1, ..1]), merged doc is primitive (p_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (p_{}), merged doc is single object (p_{})', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object (p_[{}1]), merged doc is single object (p_[{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (p_[{}, ...1]), merged doc is the same (p_[{}, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | undefined | undefined + * f_[] | undefined | f_[] + * f_p1 | undefined | f_p1 + * f_[p1] | undefined | f_[p1] + * f_[p1, ...1] | undefined | f_[p1, ...1] + * f_{}1 | undefined | f_{}1 + * f_[{}1] | undefined | f_{}1 + * f_[{}1, ...1] | undefined | f_[{}1, ...1] + */ + describe('flattened object keys in the _source document', () => { + /** fields is "undefined" for all tests below */ + const fields: SignalSourceHit['fields'] = {}; + + test('when source is an empty array (f_[]), merged doc is empty array (f_[])"', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (f_p1), merged doc is primitive (f_p1)', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (f_[p1]), merged doc is primitive (f_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (f_[p1, ...1]), merged doc is primitive (f_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (f_{}1), merged doc is single object (f_{}1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object ([f_{}1]), merged doc is single object ([f_{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (f_[{}1, ...1]), merged doc is the same (f_[{}1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + describe('fields is "[]"', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[] | undefined + * p_[] | f_[] | p_[] + * p_p1 | f_[] | p_p1 + * p_[p1] | f_[] | p_[p1] + * p_[p1, ...1] | f_[] | p_[p1, ...1] + * p_{}1 | f_[] | p_{}1 + * p_[{}1] | f_[] | p_{}1 + * p_[{}1, ...1] | f_[] | p_[{}1, ...1] + */ + describe('primitive keys in the _source document', () => { + /** fields is a flattened object key and an empty array value (p_[]) */ + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [], + }; + + test('when source is an empty array (p_[]), merged doc is empty array (p_[])"', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (p_p1), merged doc is primitive (p_p1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (p_[p1]), merged doc is primitive (p_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value'] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (p_[p1, ..1]), merged doc is primitive (p_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value_1', 'value_2'] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (p_{}), merged doc is single object (p_{})', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: { mars: 'some value' } }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object (p_[{}1]), merged doc is single object (p_[{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: 'some value' }] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (p_[{}, ...1]), merged doc is the same (p_[{}, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: 'some value' }, { mars: 'some other value' }] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[] | undefined + * f_[] | f_[] | f_[] + * f_p1 | f_[] | f_p1 + * f_[p1] | f_[] | f_[p1] + * f_[p1, ...1] | f_[] | f_[p1, ...1] + * f_{}1 | f_[] | f_{}1 + * f_[{}1] | f_[] | f_{}1 + * f_[{}1, ...1] | f_[] | f_[{}1, ...1] + */ + describe('flattened object keys in the _source document', () => { + /** fields is flattened object key with an empty array for a value (f_[]) */ + const fields: SignalSourceHit['fields'] = { + 'bar.foo': [], + }; + + test('when source is an empty array (f_[]), merged doc is empty array (f_[])"', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (f_p1), merged doc is primitive (f_p1)', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (f_[p1]), merged doc is primitive (f_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (f_[p1, ...1]), merged doc is primitive (f_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (f_{}1), merged doc is single object (f_{}1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object ([f_{}1]), merged doc is single object ([f_{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (f_[{}1, ...1]), merged doc is the same (f_[{}1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[p2] | p_p2 <-- Unboxed from array + * undefined | f_[p2, ...2] | p_[p2, ...2] + * undefined | f_[{}2] | p_{}2 <-- Unboxed from array + * undefined | f_[{}2, ...2] | p_[{}2, ...2] + */ + describe('source is "undefined"', () => { + /** _source is "undefined" for all tests below */ + const _source: SignalSourceHit['_source'] = {}; + + test('fields is a single primitive value (f_[p2]), merged doc is an unboxed array element p_p2"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: 'other_value_1', + }, + }); + }); + + test('fields is a multiple primitive values (f_[p2, ...2]), merged doc is the array (f_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: ['other_value_1', 'other_value_2'], + }, + }); + }); + + test('fields is a single nested field value (f_[{}2]), merged doc is the unboxed array element (p_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: { zed: 'other_value_1' } }, + }); + }); + + test('fields is multiple nested field values (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + describe('source is either primitive or flattened keys, with primitive values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_p1 | f_[p2] | p_p2 <-- Unboxed from array + * p_p1 | f_[p2, ...2] | p_[p2, ...2] + * p_p1 | f_[{}2] | p_{}2 <-- Unboxed from array + * p_p1 | f_[{}2, ...2] | p_[{}2, ...2] + */ + describe('primitive keys in the _source document with the value of "value" (p_p1)', () => { + /** _source is a single primitive key with a primitive value for all tests below (p_p1) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value' }, + }; + + test('fields is single array primitive value (f_[p2]), merged doc is unboxed primitive key and value (p_p2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: 'other_value_1', + }, + }); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the array (p_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: ['other_value_1', 'other_value_2'] }, + }); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array (p_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: 'other_value_1', + }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }], + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_p1 | f_[p2] | f_p2 <-- Unboxed from array + * f_p1 | f_[p2, ...2] | f_[p2, ...2] + * f_p1 | f_[{}2] | f_{}2 <-- Unboxed from array + * f_p1 | f_[{}2, ...2] | f_[{}2, ...2] + */ + describe('flattened object keys in the _source document (f_p1)', () => { + /** _source is a flattened object key with a primitive value for all tests below (f_p1) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': 'value', + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is unboxed primitive key and value (f_p2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'foo.bar': 'other_value_1', + }); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (f_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array (f_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'foo.bar': { zed: 'other_value_1' }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + }); + + describe('source is either primitive or flattened keys, with primitive array values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[] | f_[p2] | p_[p2] + * p_[] | f_[p2, ...2] | p_[p2, ...2] + * p_[] | f_[{}2] | p_[{}2] + * p_[] | f_[{}2, ...2] | p_[{}2, ...2] + */ + describe('primitive keys in the _source document with empty array (p_[])', () => { + /** _source is a primitive key with an empty array for all tests below (p_[]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [] }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array value (p_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: ['other_value_1'] }, + }); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the array (p_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: ['other_value_1', 'other_value_2'] }, + }); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }] }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[] | f_[p2] | f_[p2] + * f_[] | f_[p2, ...2] | f_[p2, ...2] + * f_[] | f_[{}2] | f_[{}2] + * f_[] | f_[{}2, ...2] | f_[{}2, ...2] + */ + describe('flattened object keys in the _source document with empty array (f_[])', () => { + /** _source is a flattened object key with an empty array for all tests below (f_[]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array (f_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple primitive values (f_[p2, ...2]), merged doc is the array (f_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[p1] | f_[p2] | p_[p2] + * p_[p1] | f_[p2, ...2] | p_[p2, ...2] + * p_[p1] | f_[{}2] | p_[{}2] + * p_[p1] | f_[{}2, ...2] | p_[{}2, ...2] + */ + describe('primitive keys in the _source document with single primitive value in an array (p_[p1])', () => { + /** _source is a primitive key with a single primitive array value for all tests below (p_[p1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value'] }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the array value (p_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: ['other_value_1'] }, + }); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (p_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: ['other_value_1', 'other_value_2'] }, + }); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (p_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }] }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[p1] | f_[p2] | f_[p2] + * f_[p1] | f_[p2, ...2] | f_[p2, ...2] + * f_[p1] | f_[{}2] | f_[{}2] + * f_[p1] | f_[{}2, ...2] | f_[{}2, ...2] + */ + describe('flattened keys in the _source document with single flattened value in an array (f_[p1])', () => { + /** _source is a flattened object key with a single primitive value for all tests below (f_p[p1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value'], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array value (f_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (f_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[p1, ...1] | f_[p2] | p_[p2] + * p_[p1, ...1] | f_[p2, ...2] | p_[p2, ...2] + * p_[p1, ...1] | f_[{}2] | p_[{}2] + * p_[p1, ...1] | f_[{}2, ...2] | p_[{}2, ...2] + */ + describe('primitive keys in the _source document with multiple array values in an array (p_[p1, ...1])', () => { + /** _source is a primitive key with an array of 2 or more elements for all tests below (p_[p1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { + bar: ['value_1', 'value_2'], + }, + }; + + test('fields is single array value (f_[p2]), merged doc is array (p_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: ['other_value_1'], + }, + }); + }); + + test('fields is multiple primitive values (f_[p2, ...2]), merged doc is the array (p_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: ['other_value_1', 'other_value_2'], + }, + }); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed value (p_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: [{ zed: 'other_value_1' }], + }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[p1, ...1] | f_[p2] | f_[p2] + * f_[p1, ...1] | f_[p2, ...2] | f_[p2, ...2] + * f_[p1, ...1] | f_[{}2] | f_[{}2] + * f_[p1, ...1] | f_[{}2, ...2] | f_[{}2, ...2] + */ + describe('flattened keys in the _source document with multiple array values in an array (f_[p1, ...1])', () => { + /** _source is a flattened object key with an array of 2 or more elements for all tests below (f_[p1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is unboxed primitive key and value (f_p2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple primitive values (f_[p2, ...2]), merged doc is the array (f_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + }); + + describe('source is either primitive or flattened keys, with object values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_{}1 | f_[p2] | p_{}1 + * p_{}1 | f_[p2, ...2] | p_{}1 + * p_{}1 | f_[{}2] | p_{}2 <-- Copied and unboxed array since we detected a nested field + * p_{}1 | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('primitive keys in the _source document with the value of "value" (p_{}1)', () => { + /** _source is a primitive key with an object value for all tests below (p_{}1) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: { mars: 'value_1' } }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same source (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the same _source (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array value (p_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: { zed: 'other_value_1' } }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_{}1 | f_[p2] | f_{}1 + * f_{}1 | f_[p2, ...2] | f_{}1 + * f_{}1 | f_[{}2] | f_{}2 <-- Copied and unboxed array since we detected a nested field + * f_{}1 | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('flattened object keys in the _source document with the value of "value" (f_{}1)', () => { + /** _source is a flattened object key with an object value for all tests below (f_{}1) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': { mars: 'value_1' }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same source (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is unboxed array value (f_{}2)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'foo.bar': { mars: 'other_value_1' }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }); + }); + }); + }); + + describe('source is either primitive or flattened keys, with object array values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[{}1] | f_[p2] | p_[{}1] + * p_[{}1] | f_[p2, ...2] | p_[{}1] + * p_[{}1] | f_[{}2] | p_[{}2] <-- Copied since we detected a nested field + * p_[{}1] | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('primitive keys in the _source document with a single array value with an object (p_[{}1])', () => { + /** _source is a primitive key with a single array value with an object for all tests below (p_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: ['value_1'] }] }, + }; + + test('fields has a single primitive value (f_[p2]), merged doc is the same _source (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has 2 or more primitive values (f_[p2, ...2]), merged doc is the same _source (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }] }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[{}1, ...1] | f_[p2] | p_[{}1, ...1] + * p_[{}1, ...1] | f_[p2, ...2] | p_[{}1, ...1] + * p_[{}1, ...1] | f_[{}2] | p_[{}2] <-- Copied since we detected a nested field + * p_[{}1, ...1] | f_[{}2, ...2] | p_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('primitive keys in the _source document with multiple array objects (p_[{}1, ...1])', () => { + /** _source is a primitive key with a 2 or more array values with an object for all tests below (p_[{}1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: ['value_1'] }, { mars: ['value_1'] }] }, + }; + + test('fields has a single primitive value (f_[p2]), merged doc is the same _source (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has 2 or more primitive values (f_[p2, ...2]), merged doc is the same _source (p_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }] }, + }); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: [{ zed: 'other_value_1' }, { zed: 'other_value_2' }] }, + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[{}1] | f_[p2] | f_[{}1] + * f_[{}1] | f_[p2, ...2] | f_[{}1] + * f_[{}1] | f_[{}2] | f_[{}2] <-- Copied since we detected a nested field + * f_[{}1] | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('flattened object keys in the _source document with the single value of "value" (f_[{}1])', () => { + /** _source is a flattened object key with a single array object for all tests below (f_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [{ mars: 'value_1' }], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same _source (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is array value (f_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[{}1, ...1] | f_[p2] | f_[{}1, ...1] + * f_[{}1, ...1] | f_[p2, ...2] | f_[{}1, ...1] + * f_[{}1, ...1] | f_[{}2] | f_[{}2] <-- Copied since we detected a nested field + * f_[{}1, ...1] | f_[{}2, ...2] | f_[{}2, ...2] <-- Copied since we detected a nested field + */ + describe('flattened object keys in the _source document with multiple values of "value" (f_[{}1, ...1])', () => { + /** _source is a flattened object key with 2 or more array objects for all tests below (f_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [{ mars: 'value_1' }, { mars: 'value_2' }], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same _source (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is array value (f_[{}2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(fields); + }); + }); + }); + + /** + * It is possible to have a mixture of flattened keys and primitive keys within a _source document. + * These tests cover those cases and these test cases should be considered hopefully rare occurrences. + * If these become more common place, update the top table with all the permutations and combinations + * of tests for these. For now, expect the flattened object keys to get the values added to them vs. + * the other value. These tests show the behaviors of this but also the existing bugs of what happens + * when we merge. + */ + describe('miscellaneous tests of mixed flattened and source objects within _source', () => { + /** _source has a primitive key mixed with an object with the same path information which causes ambiguity */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value_1' }, + 'foo.bar': 'value_2', + }; + + test('fields has a single primitive value f_[p2] which is to override one of the values above"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: 'value_1' }, + 'foo.bar': 'other_value_1', + }); + }); + + /** + * This is an ambiguous situation in which we produce incorrect results. + */ + test('fields has the same list of values as that of the original document and we actually do not understand if this is a new value or not"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: 'value_1' }, // <--- We have duplicated value_1 twice which is a bug + 'foo.bar': ['value_1', 'value_2'], // <-- We have merged the array value because we do not understand if we should or not + }); + }); + }); + + /** + * These tests show the behaviors around overriding fields with other fields such as objects overriding + * values and values overriding objects. This occurs with multi fields where you can have "foo" and "foo.keyword" + * in the fields + */ + describe('Fields overriding fields', () => { + describe('primitive keys for the _source', () => { + test('removes multi-field values such "foo.keyword" mixed with "foo" and prefers just "foo" for 1st level', () => { + const _source: SignalSourceHit['_source'] = { + foo: 'foo_value_1', + bar: 'bar_value_1', + }; + const fields: SignalSourceHit['fields'] = { + foo: ['foo_other_value_1'], + 'foo.keyword': ['foo_other_value_keyword_1'], + bar: ['bar_other_value_1'], + 'bar.keyword': ['bar_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: 'foo_other_value_1', + bar: 'bar_other_value_1', + }); + }); + + test('removes multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => { + const _source: SignalSourceHit['_source'] = { + host: { + name: 'host_value_1', + hostname: 'host_name_value_1', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'host.name': ['host_name_other_value_1'], + 'host.name.keyword': ['host_name_other_value_keyword_1'], + 'host.hostname': ['hostname_other_value_1'], + 'host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + host: { + hostname: 'hostname_other_value_1', + name: 'host_name_other_value_1', + }, + }); + }); + + test('removes multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => { + const _source: SignalSourceHit['_source'] = { + foo: { + host: { + name: 'host_value_1', + hostname: 'host_name_value_1', + }, + }, + }; + const fields: SignalSourceHit['fields'] = { + 'foo.host.name': ['host_name_other_value_1'], + 'foo.host.name.keyword': ['host_name_other_value_keyword_1'], + 'foo.host.hostname': ['hostname_other_value_1'], + 'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + host: { + hostname: 'hostname_other_value_1', + name: 'host_name_other_value_1', + }, + }, + }); + }); + + test('multi-field values mixed with regular values will not be merged accidentally"', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: ['other_value_1'], + 'foo.bar': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: 'other_value_1', + }); + }); + }); + + describe('flattened keys for the _source', () => { + test('removes multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => { + const _source: SignalSourceHit['_source'] = { + 'host.name': 'host_value_1', + 'host.hostname': 'host_name_value_1', + }; + const fields: SignalSourceHit['fields'] = { + 'host.name': ['host_name_other_value_1'], + 'host.name.keyword': ['host_name_other_value_keyword_1'], + 'host.hostname': ['hostname_other_value_1'], + 'host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'host.name': 'host_name_other_value_1', + 'host.hostname': 'hostname_other_value_1', + }); + }); + + test('removes multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.host.name': 'host_value_1', + 'foo.host.hostname': 'host_name_value_1', + }; + const fields: SignalSourceHit['fields'] = { + 'foo.host.name': ['host_name_other_value_1'], + 'foo.host.name.keyword': ['host_name_other_value_keyword_1'], + 'foo.host.hostname': ['hostname_other_value_1'], + 'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + 'foo.host.name': 'host_name_other_value_1', + 'foo.host.hostname': 'hostname_other_value_1', + }); + }); + + test('invalid fields of several levels mixed with regular values will not be merged accidentally due to runtime fields being liberal"', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: ['other_value_1'], + 'foo.bar': ['other_value_2'], + 'foo.bar.zed': ['zed_other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: 'other_value_1', + }); + }); + }); + }); + + /** + * These tests are around parent objects that are not nested but are array types. We do not try to merge + * into these as this causes ambiguities between array types and object types. + */ + describe('parent array objects', () => { + test('parent array objects will not be overridden since that is an ambiguous use case for a top level value', () => { + const _source: SignalSourceHit['_source'] = { + foo: [ + { + bar: 'value_1', + mars: ['value_1'], + }, + ], + }; + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + 'foo.mars': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('parent array objects will not be overridden since that is an ambiguous use case for a deeply nested value', () => { + const _source: SignalSourceHit['_source'] = { + foo: { + zed: [ + { + bar: 'value_1', + mars: ['value_1'], + }, + ], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'foo.zed.bar': ['other_value_1'], + 'foo.zed.mars': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * Specific tests around nested field types such as ensuring we are unboxing when we can + */ + describe('nested fields', () => { + test('unboxes deeply nested fields from a single array items when source is non-existent', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: ['single_value'], zed: ['single_value'] }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { bar: 'single_value', zed: 'single_value' }, + }); + }); + + test('does not unbox when source is exists and has arrays for the same values with primitive keys', () => { + const _source: SignalSourceHit['_source'] = { + foo: [ + { + bar: [], + zed: [], + }, + ], + }; + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: ['single_value'], zed: ['single_value'] }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeAllFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: [{ bar: ['single_value'], zed: ['single_value'] }], + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.ts new file mode 100644 index 0000000000000..de8d3ba820e23 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_all_fields_with_source.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash/fp'; +import { set } from '@elastic/safer-lodash-set/fp'; +import { SignalSource, SignalSourceHit } from '../../types'; +import { filterFieldEntries } from '../utils/filter_field_entries'; +import type { FieldsType } from '../types'; +import { isObjectLikeOrArrayOfObjectLikes } from '../utils/is_objectlike_or_array_of_objectlikes'; +import { isNestedObject } from '../utils/is_nested_object'; +import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; +import { isPrimitive } from '../utils/is_primitive'; +import { isArrayOfPrimitives } from '../utils/is_array_of_primitives'; +import { arrayInPathExists } from '../utils/array_in_path_exists'; +import { isTypeObject } from '../utils/is_type_object'; + +/** + * Merges all of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information + * on this function and the general strategies. + * + * @param doc The document with "_source" and "fields" + * @param throwOnFailSafe Defaults to false, but if set to true it will cause a throw if the fail safe is triggered to indicate we need to add a new explicit test condition + * @returns The two merged together in one object where we can + */ +export const mergeAllFieldsWithSource = ({ doc }: { doc: SignalSourceHit }): SignalSourceHit => { + const source = doc._source ?? {}; + const fields = doc.fields ?? {}; + const fieldEntries = Object.entries(fields); + const filteredEntries = filterFieldEntries(fieldEntries); + + const transformedSource = filteredEntries.reduce( + (merged, [fieldsKey, fieldsValue]: [string, FieldsType]) => { + if ( + hasEarlyReturnConditions({ + fieldsValue, + fieldsKey, + merged, + }) + ) { + return merged; + } + + const valueInMergedDocument = get(fieldsKey, merged); + if (valueInMergedDocument === undefined) { + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + } else if (isPrimitive(valueInMergedDocument)) { + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + } else if (isArrayOfPrimitives(valueInMergedDocument)) { + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + } else if ( + isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) && + isNestedObject(fieldsValue) && + !Array.isArray(valueInMergedDocument) + ) { + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + } else if ( + isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) && + isNestedObject(fieldsValue) && + Array.isArray(valueInMergedDocument) + ) { + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + } else { + // fail safe catch all condition for production, but we shouldn't try to reach here and + // instead write tests if we encounter this situation. + return merged; + } + }, + { ...source } + ); + + return { + ...doc, + _source: transformedSource, + }; +}; + +/** + * Returns true if any early return conditions are met which are + * - If the fieldsValue is an empty array return + * - If we have an array within the path return and the value in our merged documented is non-existent + * - If the value is an object or is an array of object types and we don't have a nested field + * @param fieldsValue The field value to check + * @param fieldsKey The key of the field we are checking + * @param merged The merge document which is what we are testing conditions against + * @returns true if we should return early, otherwise false + */ +const hasEarlyReturnConditions = ({ + fieldsValue, + fieldsKey, + merged, +}: { + fieldsValue: FieldsType; + fieldsKey: string; + merged: SignalSource; +}) => { + const valueInMergedDocument = get(fieldsKey, merged); + return ( + fieldsValue.length === 0 || + (valueInMergedDocument === undefined && arrayInPathExists(fieldsKey, merged)) || + (isObjectLikeOrArrayOfObjectLikes(valueInMergedDocument) && + !isNestedObject(fieldsValue) && + !isTypeObject(fieldsValue)) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts new file mode 100644 index 0000000000000..70d1e79580e84 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.test.ts @@ -0,0 +1,1379 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mergeMissingFieldsWithSource } from './merge_missing_fields_with_source'; +import { SignalSourceHit } from '../../types'; +import { emptyEsResult } from '../../__mocks__/empty_signal_source_hit'; + +/** + * See ../README.md for the nomenclature of any notes within tests below + */ +describe('merge_missing_fields_with_source', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + /** Get the return type of the mergeMissingFieldsWithSource for TypeScript checks against expected */ + type ReturnTypeMergeFieldsWithSource = ReturnType['_source']; + + describe('fields is "undefined"', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | undefined | undefined + * p_[] | undefined | p_[] + * p_p1 | undefined | p_p1 + * p_[p1] | undefined | p_[p1] + * p_[p1, ...1] | undefined | p_[p1, ...1] + * p_{}1 | undefined | p_{}1 + * p_[{}1] | undefined | p_{}1 + * p_[{}1, ...1] | undefined | p_[{}1, ...1] + */ + describe('primitive keys in the _source document', () => { + /** fields is "undefined" for all tests below */ + const fields: SignalSourceHit['fields'] = {}; + + test('when source is "undefined", merged doc is "undefined"', () => { + const _source: SignalSourceHit['_source'] = {}; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an empty array (p_[]), merged doc is empty array (p_[])"', () => { + const _source: SignalSourceHit['_source'] = { + foo: [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (p_p1), merged doc is primitive (p_p1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (p_[p1]), merged doc is primitive (p_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (p_[p1, ..1]), merged doc is primitive (p_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (p_{}), merged doc is single object (p_{})', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object (p_[{}1]), merged doc is single object (p_[{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (p_[{}, ...1]), merged doc is the same (p_[{}, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | undefined | undefined + * f_[] | undefined | f_[] + * f_p1 | undefined | f_p1 + * f_[p1] | undefined | f_[p1] + * f_[p1, ...1] | undefined | f_[p1, ...1] + * f_{}1 | undefined | f_{}1 + * f_[{}1] | undefined | f_{}1 + * f_[{}1, ...1] | undefined | f_[{}1, ...1] + */ + describe('flattened object keys in the _source document', () => { + /** fields is "undefined" for all tests below */ + const fields: SignalSourceHit['fields'] = {}; + + test('when source is an empty array (f_[]), merged doc is empty array (f_[])"', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (f_p1), merged doc is primitive (f_p1)', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (f_[p1]), merged doc is primitive (f_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (f_[p1, ...1]), merged doc is primitive (f_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (f_{}1), merged doc is single object (f_{}1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object ([f_{}1]), merged doc is single object ([f_{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (f_[{}1, ...1]), merged doc is the same (f_[{}1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + describe('fields is "[]"', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[] | undefined + * p_[] | f_[] | p_[] + * p_p1 | f_[] | p_p1 + * p_[p1] | f_[] | p_[p1] + * p_[p1, ...1] | f_[] | p_[p1, ...1] + * p_{}1 | f_[] | p_{}1 + * p_[{}1] | f_[] | p_{}1 + * p_[{}1, ...1] | f_[] | p_[{}1, ...1] + */ + describe('primitive keys in the _source document', () => { + /** fields is a flattened object key and an empty array value (p_[]) */ + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [], + }; + + test('when source is an empty array (p_[]), merged doc is empty array (p_[])"', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (p_p1), merged doc is primitive (p_p1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (p_[p1]), merged doc is primitive (p_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value'] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (p_[p1, ..1]), merged doc is primitive (p_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value_1', 'value_2'] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (p_{}), merged doc is single object (p_{})', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: { mars: 'some value' } }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object (p_[{}1]), merged doc is single object (p_[{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: 'some value' }] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (p_[{}, ...1]), merged doc is the same (p_[{}, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: 'some value' }, { mars: 'some other value' }] }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[] | undefined + * f_[] | f_[] | f_[] + * f_p1 | f_[] | f_p1 + * f_[p1] | f_[] | f_[p1] + * f_[p1, ...1] | f_[] | f_[p1, ...1] + * f_{}1 | f_[] | f_{}1 + * f_[{}1] | f_[] | f_{}1 + * f_[{}1, ...1] | f_[] | f_[{}1, ...1] + */ + describe('flattened object keys in the _source document', () => { + /** fields is flattened object key with an empty array for a value (f_[]) */ + const fields: SignalSourceHit['fields'] = { + 'bar.foo': [], + }; + + test('when source is an empty array (f_[]), merged doc is empty array (f_[])"', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': [], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a primitive (f_p1), merged doc is primitive (f_p1)', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': 'value', + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with a single primitive (f_[p1]), merged doc is primitive (f_[p1])', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': ['value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with 2 or more primitives (f_[p1, ...1]), merged doc is primitive (f_[p1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + 'bar.foo': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is a single object (f_{}1), merged doc is single object (f_{}1)', () => { + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'some value' }, + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array with single object ([f_{}1]), merged doc is single object ([f_{}1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('when source is an array of 1 or more objects (f_[{}1, ...1]), merged doc is the same (f_[{}1, ...1])', () => { + const _source: SignalSourceHit['_source'] = { + foo: [{ bar: 'some value' }, { foo: 'some other value' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * undefined | f_[p2] | p_p2 <-- Unboxed from array + * undefined | f_[p2, ...2] | p_[p2, ...2] + * undefined | f_[{}2] | {} <-- We have an empty object since we only merge primitives + * undefined | f_[{}2, ...2] | {} <-- We have an empty object since we only merge primitives + */ + describe('source is "undefined"', () => { + /** _source is "undefined" for all tests below */ + const _source: SignalSourceHit['_source'] = {}; + + test('fields is a single primitive value (f_[p2]), merged doc is an unboxed array element p_p2"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: 'other_value_1', + }, + }); + }); + + test('fields is a multiple primitive values (f_[p2, ...2]), merged doc is the array (p_[p2, ...2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: { + bar: ['other_value_1', 'other_value_2'], + }, + }); + }); + + test('fields is a single nested field value (f_[{}2]), merged doc is empty object ({})"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({}); + }); + + test('fields is multiple nested field values (f_[{}2, ...2]), merged doc is the empty object ({})"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({}); + }); + }); + + describe('source is either primitive or flattened keys, with primitive values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_p1 | f_[p2] | p_p1 + * p_p1 | f_[p2, ...2] | p_p1 + * p_p1 | f_[{}2] | p_p1 + * p_p1 | f_[{}2, ...2] | p_p1 + */ + describe('primitive keys in the _source document with the value of "value" (p_p1)', () => { + /** _source is a single primitive key with a primitive value for all tests below (p_p1) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value' }, + }; + + test('fields is single array primitive value (f_[p2]), merged doc is unboxed primitive key and value (p_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the array (p_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array (p_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: 'other_value_1' }, { bar: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_p1 | f_[p2] | f_p1 + * f_p1 | f_[p2, ...2] | f_p1 + * f_p1 | f_[{}2] | f_p1 + * f_p1 | f_[{}2, ...2] | f_p1 + */ + describe('flattened object keys in the _source document (f_p1)', () => { + /** _source is a flattened object key with a primitive value for all tests below (f_p1) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': 'value', + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is unboxed primitive key and value (f_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (f_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array (f_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_p1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + describe('source is either primitive or flattened keys, with primitive array values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[] | f_[p2] | p_[] + * p_[] | f_[p2, ...2] | p_[] + * p_[] | f_[{}2] | p_[] + * p_[] | f_[{}2, ...2] | p_[] + */ + describe('primitive keys in the _source document with empty array (p_[])', () => { + /** _source is a primitive key with an empty array for all tests below (p_[]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [] }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array value (p_[]])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the array (p_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[] | f_[p2] | f_[] + * f_[] | f_[p2, ...2] | f_[] + * f_[] | f_[{}2] | f_[] + * f_[] | f_[{}2, ...2] | f_[] + */ + describe('flattened object keys in the _source document with empty array (f_[])', () => { + /** _source is a flattened object key with an empty array for all tests below (f_[]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array (f_[p2])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple primitive values (f_[p2, ...2]), merged doc is the array (f_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[p1] | f_[p2] | p_[p1] + * p_[p1] | f_[p2, ...2] | p_[p1] + * p_[p1] | f_[{}2] | p_[p1] + * p_[p1] | f_[{}2, ...2] | p_[p1] + */ + describe('primitive keys in the _source document with single primitive value in an array (p_[p1])', () => { + /** _source is a primitive key with a single primitive array value for all tests below (p_[p1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: ['value'] }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the array value (p_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (p_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (p_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[p1] | f_[p2] | f_[p1] + * f_[p1] | f_[p2, ...2] | f_[p1] + * f_[p1] | f_[{}2] | f_[p1] + * f_[p1] | f_[{}2, ...2] | f_[p1] + */ + describe('flattened keys in the _source document with single flattened value in an array (f_[p1])', () => { + /** _source is a flattened object key with a single primitive value for all tests below (f_p[p1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value'], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is array value (f_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive value (f_[p2, ...2]), merged doc is the array (f_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[p1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[p1, ...1] | f_[p2] | p_[p1, ...1] + * p_[p1, ...1] | f_[p2, ...2] | p_[p1, ...1] + * p_[p1, ...1] | f_[{}2] | p_[p1, ...1] + * p_[p1, ...1] | f_[{}2, ...2] | p_[p1, ...1] + */ + describe('primitive keys in the _source document with multiple array values in an array (p_[p1, ...1])', () => { + /** _source is a primitive key with an array of 2 or more elements for all tests below (p_[p1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { + bar: ['value_1', 'value_2'], + }, + }; + + test('fields is single array value (f_[p2]), merged doc is array (p_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields is multiple primitive values (f_[p2, ...2]), merged doc is the array (p_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed value (p_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[p1, ...1] | f_[p2] | f_[p1, ...1] + * f_[p1, ...1] | f_[p2, ...2] | f_[p1, ...1] + * f_[p1, ...1] | f_[{}2] | f_[p1, ...1] + * f_[p1, ...1] | f_[{}2, ...2] | f_[p1, ...1] + */ + describe('flattened keys in the _source document with multiple array values in an array (f_[p1, ...1])', () => { + /** _source is a flattened object key with an array of 2 or more elements for all tests below (f_[p1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is unboxed primitive key and value (f_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple primitive values (f_[p2, ...2]), merged doc is the array (f_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array (f_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[p1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + describe('source is either primitive or flattened keys, with object values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_{}1 | f_[p2] | p_{}1 + * p_{}1 | f_[p2, ...2] | p_{}1 + * p_{}1 | f_[{}2] | p_{}1 + * p_{}1 | f_[{}2, ...2] | p_{}1 + */ + describe('primitive keys in the _source document with the value of "value" (p_{}1)', () => { + /** _source is a primitive key with an object value for all tests below (p_{}1) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: { mars: 'value_1' } }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same source (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has single primitive values (f_[p2, ...2]), merged doc is the same _source (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the unboxed array value (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_{}1 | f_[p2] | f_{}1 + * f_{}1 | f_[p2, ...2] | f_{}1 + * f_{}1 | f_[{}2] | f_{}1 + * f_{}1 | f_[{}2, ...2] | f_{}1 + */ + describe('flattened object keys in the _source document with the value of "value" (f_{}1)', () => { + /** _source is a flattened object key with an object value for all tests below (f_{}1) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': { mars: 'value_1' }, + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same source (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is unboxed array value (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_{}1)"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + describe('source is either primitive or flattened keys, with object array values', () => { + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[{}1] | f_[p2] | p_[{}1] + * p_[{}1] | f_[p2, ...2] | p_[{}1] + * p_[{}1] | f_[{}2] | p_[{}1] + * p_[{}1] | f_[{}2, ...2] | p_[{}1] + */ + describe('primitive keys in the _source document with a single array value with an object (p_[{}1])', () => { + /** _source is a primitive key with a single array value with an object for all tests below (p_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: ['value_1'] }] }, + }; + + test('fields has a single primitive value (f_[p2]), merged doc is the same _source (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has 2 or more primitive values (f_[p2, ...2]), merged doc is the same _source (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * p_[{}1, ...1] | f_[p2] | p_[{}1, ...1] + * p_[{}1, ...1] | f_[p2, ...2] | p_[{}1, ...1] + * p_[{}1, ...1] | f_[{}2] | p_[{}1, ...1] + * p_[{}1, ...1] | f_[{}2, ...2] | p_[{}1, ...1] + */ + describe('primitive keys in the _source document with multiple array objects (p_[{}1, ...1])', () => { + /** _source is a primitive key with a 2 or more array values with an object for all tests below (p_[{}1, ...1]) */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: [{ mars: ['value_1'] }, { mars: ['value_1'] }] }, + }; + + test('fields has a single primitive value (f_[p2]), merged doc is the same _source (p_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has 2 or more primitive values (f_[p2, ...2]), merged doc is the same _source (p_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is the array value (p_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (p_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ zed: 'other_value_1' }, { zed: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[{}1] | f_[p2] | f_[{}1] + * f_[{}1] | f_[p2, ...2] | f_[{}1] + * f_[{}1] | f_[{}2] | f_[{}1] + * f_[{}1] | f_[{}2, ...2] | f_[{}1] + */ + describe('flattened object keys in the _source document with the single value of "value" (f_[{}1])', () => { + /** _source is a flattened object key with a single array object for all tests below (f_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [{ mars: 'value_1' }], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same _source (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is array value (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * source | fields | value after merge + * ----- | --------- | ----- + * f_[{}1, ...1] | f_[p2] | f_[{}1, ...1] + * f_[{}1, ...1] | f_[p2, ...2] | f_[{}1, ...1] + * f_[{}1, ...1] | f_[{}2] | f_[{}1, ...1] + * f_[{}1, ...1] | f_[{}2, ...2] | f_[{}1, ...1] + */ + describe('flattened object keys in the _source document with multiple values of "value" (f_[{}1, ...1])', () => { + /** _source is a flattened object key with 2 or more array objects for all tests below (f_[{}1]) */ + const _source: SignalSourceHit['_source'] = { + 'foo.bar': [{ mars: 'value_1' }, { mars: 'value_2' }], + }; + + test('fields is flattened object key with single array value (f_[p2]), merged doc is the same _source (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has primitive values (f_[p2, ...2]), merged doc is the same _source (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1', 'other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has a single nested object (f_[{}2]), merged doc is array value (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('fields has multiple nested objects (f_[{}2, ...2]), merged doc is the array (f_[{}1, ...1])"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': [{ mars: 'other_value_1' }, { mars: 'other_value_2' }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + }); + + /** + * It is possible to have a mixture of flattened keys and primitive keys within a _source document. + * These tests cover those cases and these test cases should be considered hopefully rare occurrences. + * If these become more common place, update the top table with all the permutations and combinations + * of tests for these. For now, expect the flattened object keys to get the values added to them vs. + * the other value. These tests show the behaviors of this but also the existing bugs of what happens + * when we merge. + */ + describe('miscellaneous tests of mixed flattened and source objects within _source', () => { + /** _source has a primitive key mixed with an object with the same path information which causes ambiguity */ + const _source: SignalSourceHit['_source'] = { + foo: { bar: 'value_1' }, + 'foo.bar': 'value_2', + }; + + test('fields has a single primitive value f_[p2] which is not overridden"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + /** + * This is an ambiguous situation in which we produce correct results since _source is defined. + */ + test('fields has the same list of values as that of the original document"', () => { + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['value_1', 'value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * These tests show the behaviors around overriding fields with other fields such as objects overriding + * values and values overriding objects. This occurs with multi fields where you can have "foo" and "foo.keyword" + * in the fields + */ + describe('Fields overriding fields', () => { + describe('primitive keys for the _source', () => { + test('DOES NOT remove multi-field values such "foo.keyword" mixed with "foo" and prefers just "foo" for 1st level', () => { + const _source: SignalSourceHit['_source'] = { + foo: 'foo_value_1', + bar: 'bar_value_1', + }; + const fields: SignalSourceHit['fields'] = { + foo: ['foo_other_value_1'], + 'foo.keyword': ['foo_other_value_keyword_1'], + bar: ['bar_other_value_1'], + 'bar.keyword': ['bar_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('DOES NOT remove multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => { + const _source: SignalSourceHit['_source'] = { + host: { + name: 'host_value_1', + hostname: 'host_name_value_1', + }, + }; + const fields: SignalSourceHit['fields'] = { + 'host.name': ['host_name_other_value_1'], + 'host.name.keyword': ['host_name_other_value_keyword_1'], + 'host.hostname': ['hostname_other_value_1'], + 'host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('DOES NOT remove multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => { + const _source: SignalSourceHit['_source'] = { + foo: { + host: { + name: 'host_value_1', + hostname: 'host_name_value_1', + }, + }, + }; + const fields: SignalSourceHit['fields'] = { + 'foo.host.name': ['host_name_other_value_1'], + 'foo.host.name.keyword': ['host_name_other_value_keyword_1'], + 'foo.host.hostname': ['hostname_other_value_1'], + 'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('multi-field values mixed with regular values will not be merged accidentally"', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: ['other_value_1'], + 'foo.bar': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: 'other_value_1', + }); + }); + }); + + describe('flattened keys for the _source', () => { + test('DOES NOT remove multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => { + const _source: SignalSourceHit['_source'] = { + 'host.name': 'host_value_1', + 'host.hostname': 'host_name_value_1', + }; + const fields: SignalSourceHit['fields'] = { + 'host.name': ['host_name_other_value_1'], + 'host.name.keyword': ['host_name_other_value_keyword_1'], + 'host.hostname': ['hostname_other_value_1'], + 'host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('DOES NOT remove multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => { + const _source: SignalSourceHit['_source'] = { + 'foo.host.name': 'host_value_1', + 'foo.host.hostname': 'host_name_value_1', + }; + const fields: SignalSourceHit['fields'] = { + 'foo.host.name': ['host_name_other_value_1'], + 'foo.host.name.keyword': ['host_name_other_value_keyword_1'], + 'foo.host.hostname': ['hostname_other_value_1'], + 'foo.host.hostname.keyword': ['hostname_other_value_keyword_1'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('invalid fields of several levels mixed with regular values will not be merged accidentally due to runtime fields being liberal"', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: ['other_value_1'], + 'foo.bar': ['other_value_2'], + 'foo.bar.zed': ['zed_other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({ + foo: 'other_value_1', + }); + }); + }); + }); + + /** + * These tests are around parent objects that are not nested but are array types. We do not try to merge + * into these as this causes ambiguities between array types and object types. + */ + describe('parent array objects', () => { + test('parent array objects will not be overridden since that is an ambiguous use case for a top level value', () => { + const _source: SignalSourceHit['_source'] = { + foo: [ + { + bar: 'value_1', + mars: ['value_1'], + }, + ], + }; + const fields: SignalSourceHit['fields'] = { + 'foo.bar': ['other_value_1'], + 'foo.mars': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + + test('parent array objects will not be overridden since that is an ambiguous use case for a deeply nested value', () => { + const _source: SignalSourceHit['_source'] = { + foo: { + zed: [ + { + bar: 'value_1', + mars: ['value_1'], + }, + ], + }, + }; + const fields: SignalSourceHit['fields'] = { + 'foo.zed.bar': ['other_value_1'], + 'foo.zed.mars': ['other_value_2'], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); + + /** + * Specific tests around nested field types such as ensuring we are unboxing when we can + */ + describe('nested fields', () => { + test('returns empty object since we only consider merging in primitive values and not nested fields', () => { + const _source: SignalSourceHit['_source'] = {}; + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: ['single_value'], zed: ['single_value'] }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual({}); + }); + + test('does not touch the source object when it is empty arrays', () => { + const _source: SignalSourceHit['_source'] = { + foo: [ + { + bar: [], + zed: [], + }, + ], + }; + const fields: SignalSourceHit['fields'] = { + foo: [{ bar: ['single_value'], zed: ['single_value'] }], + }; + const doc: SignalSourceHit = { ...emptyEsResult(), _source, fields }; + const merged = mergeMissingFieldsWithSource({ doc })._source; + expect(merged).toEqual(_source); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.ts new file mode 100644 index 0000000000000..bf541acbe7e33 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/strategies/merge_missing_fields_with_source.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash/fp'; +import { set } from '@elastic/safer-lodash-set/fp'; +import { SignalSource, SignalSourceHit } from '../../types'; +import { filterFieldEntries } from '../utils/filter_field_entries'; +import type { FieldsType } from '../types'; +import { recursiveUnboxingFields } from '../utils/recursive_unboxing_fields'; +import { isTypeObject } from '../utils/is_type_object'; +import { arrayInPathExists } from '../utils/array_in_path_exists'; +import { isNestedObject } from '../utils/is_nested_object'; + +/** + * Merges only missing sections of "doc._source" with its "doc.fields" on a "best effort" basis. See ../README.md for more information + * on this function and the general strategies. + * @param doc The document with "_source" and "fields" + * @param throwOnFailSafe Defaults to false, but if set to true it will cause a throw if the fail safe is triggered to indicate we need to add a new explicit test condition + * @returns The two merged together in one object where we can + */ +export const mergeMissingFieldsWithSource = ({ + doc, +}: { + doc: SignalSourceHit; +}): SignalSourceHit => { + const source = doc._source ?? {}; + const fields = doc.fields ?? {}; + const fieldEntries = Object.entries(fields); + const filteredEntries = filterFieldEntries(fieldEntries); + + const transformedSource = filteredEntries.reduce( + (merged, [fieldsKey, fieldsValue]: [string, FieldsType]) => { + if ( + hasEarlyReturnConditions({ + fieldsValue, + fieldsKey, + merged, + }) + ) { + return merged; + } + + const valueInMergedDocument = get(fieldsKey, merged); + const valueToMerge = recursiveUnboxingFields(fieldsValue, valueInMergedDocument); + return set(fieldsKey, valueToMerge, merged); + }, + { ...source } + ); + + return { + ...doc, + _source: transformedSource, + }; +}; + +/** + * Returns true if any early return conditions are met which are + * - If the fieldsValue is an empty array return + * - If the value to merge in is not undefined, return early + * - If an array within the path exists, do an early return + * - If the value matches a type object, do an early return + * @param fieldsValue The field value to check + * @param fieldsKey The key of the field we are checking + * @param merged The merge document which is what we are testing conditions against + * @returns true if we should return early, otherwise false + */ +const hasEarlyReturnConditions = ({ + fieldsValue, + fieldsKey, + merged, +}: { + fieldsValue: FieldsType; + fieldsKey: string; + merged: SignalSource; +}) => { + const valueInMergedDocument = get(fieldsKey, merged); + return ( + fieldsValue.length === 0 || + valueInMergedDocument !== undefined || + arrayInPathExists(fieldsKey, merged) || + isNestedObject(fieldsValue) || + isTypeObject(fieldsValue) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/types.ts new file mode 100644 index 0000000000000..e8142e41715e2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * A bit stricter typing since the default fields type is an "any" + */ +export type FieldsType = string[] | number[] | boolean[] | object[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.test.ts new file mode 100644 index 0000000000000..1b2accd0a16a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.test.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { arrayInPathExists } from './array_in_path_exists'; + +describe('array_in_path_exists', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns false when empty string and empty object', () => { + expect(arrayInPathExists('', {})).toEqual(false); + }); + + test('returns false when a path and empty object', () => { + expect(arrayInPathExists('a.b.c', {})).toEqual(false); + }); + + test('returns true when a path and an array exists', () => { + expect(arrayInPathExists('a', { a: [] })).toEqual(true); + }); + + test('returns true when a path and an array exists within the parent path at level 1', () => { + expect(arrayInPathExists('a.b', { a: [] })).toEqual(true); + }); + + test('returns true when a path and an array exists within the parent path at level 3', () => { + expect(arrayInPathExists('a.b.c', { a: [] })).toEqual(true); + }); + + test('returns true when a path and an array exists within the parent path at level 2', () => { + expect(arrayInPathExists('a.b.c', { a: { b: [] } })).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.ts new file mode 100644 index 0000000000000..b8e742fbaba61 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/array_in_path_exists.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash/fp'; +import { SignalSource } from '../../types'; + +/** + * Returns true if an array within the path exists anywhere. + * @param fieldsKey The fields key to check if an array exists along the path + * @param source The source document to check for an array anywhere along the path + * @returns true if we detect an array along the path, otherwise false + */ +export const arrayInPathExists = (fieldsKey: string, source: SignalSource): boolean => { + const splitPath = fieldsKey.split('.'); + return splitPath.some((_, index, array) => { + const newPath = [...array].splice(0, index + 1).join('.'); + return Array.isArray(get(newPath, source)); + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.test.ts new file mode 100644 index 0000000000000..9cc2478290885 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.test.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { filterFieldEntries } from './filter_field_entries'; +import { FieldsType } from '../types'; + +describe('filter_field_entries', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + /** Dummy test value */ + const dummyValue = ['value']; + + /** + * Get the return type of the mergeFieldsWithSource for TypeScript checks against expected + */ + type ReturnTypeFilterFieldEntries = ReturnType; + + test('returns a single valid fieldEntries as expected', () => { + const fieldEntries: Array<[string, FieldsType]> = [['foo.bar', dummyValue]]; + expect(filterFieldEntries(fieldEntries)).toEqual(fieldEntries); + }); + + test('removes invalid dotted entries', () => { + const fieldEntries: Array<[string, FieldsType]> = [ + ['.', dummyValue], + ['foo.bar', dummyValue], + ['..', dummyValue], + ['foo..bar', dummyValue], + ]; + expect(filterFieldEntries(fieldEntries)).toEqual([ + ['foo.bar', dummyValue], + ]); + }); + + test('removes multi-field values such "foo.keyword" mixed with "foo" and prefers just "foo" for 1st level', () => { + const fieldEntries: Array<[string, FieldsType]> = [ + ['foo', dummyValue], + ['foo.keyword', dummyValue], // <-- "foo.keyword" multi-field should be removed + ['bar.keyword', dummyValue], // <-- "bar.keyword" multi-field should be removed + ['bar', dummyValue], + ]; + expect(filterFieldEntries(fieldEntries)).toEqual([ + ['foo', dummyValue], + ['bar', dummyValue], + ]); + }); + + test('removes multi-field values such "host.name.keyword" mixed with "host.name" and prefers just "host.name" for 2nd level', () => { + const fieldEntries: Array<[string, FieldsType]> = [ + ['host.name', dummyValue], + ['host.name.keyword', dummyValue], // <-- multi-field should be removed + ['host.hostname', dummyValue], + ['host.hostname.keyword', dummyValue], // <-- multi-field should be removed + ]; + expect(filterFieldEntries(fieldEntries)).toEqual([ + ['host.name', dummyValue], + ['host.hostname', dummyValue], + ]); + }); + + test('removes multi-field values such "foo.host.name.keyword" mixed with "foo.host.name" and prefers just "foo.host.name" for 3rd level', () => { + const fieldEntries: Array<[string, FieldsType]> = [ + ['foo.host.name', dummyValue], + ['foo.host.name.keyword', dummyValue], // <-- multi-field should be removed + ['foo.host.hostname', dummyValue], + ['foo.host.hostname.keyword', dummyValue], // <-- multi-field should be removed + ]; + expect(filterFieldEntries(fieldEntries)).toEqual([ + ['foo.host.name', dummyValue], + ['foo.host.hostname', dummyValue], + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.ts new file mode 100644 index 0000000000000..221cdabc62847 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/filter_field_entries.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isMultiField } from './is_multifield'; +import { isInvalidKey } from './is_invalid_key'; +import { isTypeObject } from './is_type_object'; +import { FieldsType } from '../types'; + +/** + * Filters field entries by removing invalid field entries such as any invalid characters + * in the keys or if there are sub-objects that are trying to override regular objects and + * are invalid runtime field names. Also matches type objects such as geo-points and we ignore + * those and don't try to merge those. + * + * @param fieldEntries The field entries to filter + * @returns The field entries filtered + */ +export const filterFieldEntries = ( + fieldEntries: Array<[string, FieldsType]> +): Array<[string, FieldsType]> => { + return fieldEntries.filter(([fieldsKey, fieldsValue]: [string, FieldsType]) => { + return ( + !isInvalidKey(fieldsKey) && + !isMultiField(fieldsKey, fieldEntries) && + !isTypeObject(fieldsValue) // TODO: Look at not filtering this and instead transform it so it can be inserted correctly in the strategies which does an overwrite of everything from fields + ); + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/index.ts new file mode 100644 index 0000000000000..baf9efca511e2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './array_in_path_exists'; +export * from './filter_field_entries'; +export * from './is_array_of_primitives'; +export * from './is_invalid_key'; +export * from './is_multifield'; +export * from './is_nested_object'; +export * from './is_objectlike_or_array_of_objectlikes'; +export * from './is_primitive'; +export * from './is_type_object'; +export * from './recursive_unboxing_fields'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.test.ts new file mode 100644 index 0000000000000..22b9903d30de6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isArrayOfPrimitives } from './is_array_of_primitives'; + +describe('is_array_of_primitives', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns true when an empty array is passed in', () => { + expect(isArrayOfPrimitives([])).toEqual(true); + }); + + test('returns true when an array of primitives are passed in', () => { + expect(isArrayOfPrimitives([null, 2, 'string', 5, undefined])).toEqual(true); + }); + + /** + * Simple table test of values of primitive arrays which should all pass + */ + test.each([ + [[null]], + [[1]], + [['string']], + [['string', null, 5, false, String('hi'), Boolean(true), undefined]], + ])('returns true when a primitive array of %o is passed in', (arrayValue) => { + expect(isArrayOfPrimitives(arrayValue)).toEqual(true); + }); + + /** + * Simple table test of values of all objects which should not pass + */ + test.each([ + [[{}]], + [[{ a: 1 }]], + [[1, {}]], + [[[], 'string']], + [['string', null, 5, false, String('hi'), {}, Boolean(true), undefined]], + ])('returns false when the array of %o contains an object is passed in', (arrayValue) => { + expect(isArrayOfPrimitives(arrayValue)).toEqual(false); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.ts new file mode 100644 index 0000000000000..c65c88c40b9bb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_array_of_primitives.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SearchTypes } from '../../../../../../common/detection_engine/types'; +import { isPrimitive } from './is_primitive'; + +/** + * Returns true if this is an array and all elements of the array are primitives and not objects + * @param valueInMergedDocument The search type to check if everything is primitive or not + * @returns true if is an array and everything in the array is a primitive type + */ +export const isArrayOfPrimitives = (valueInMergedDocument: SearchTypes | null): boolean => { + return ( + Array.isArray(valueInMergedDocument) && + valueInMergedDocument.every((value) => isPrimitive(value)) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.test.ts new file mode 100644 index 0000000000000..7035732238775 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isInvalidKey } from './is_invalid_key'; + +describe('matches_invalid_key', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('it returns true if a value is a single dot', () => { + expect(isInvalidKey('.')).toEqual(true); + }); + + test('it returns true if a value starts with a dot', () => { + expect(isInvalidKey('.invalidName')).toEqual(true); + }); + + test('it returns true if a value is 2 dots', () => { + expect(isInvalidKey('..')).toEqual(true); + }); + + test('it returns true if a value is 3 dots', () => { + expect(isInvalidKey('...')).toEqual(true); + }); + + test('it returns true if a value has two dots in its name', () => { + expect(isInvalidKey('host..name')).toEqual(true); + }); + + test('it returns false if a value has a single dot', () => { + expect(isInvalidKey('host.name')).toEqual(false); + }); + + test('it returns false if a value is a regular path', () => { + expect(isInvalidKey('a.b.c.d')).toEqual(false); + }); + + /** Yes, this is a valid key in elastic */ + test('it returns false if a value ends with a dot', () => { + expect(isInvalidKey('validName.')).toEqual(false); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.ts new file mode 100644 index 0000000000000..358dc3e148547 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_invalid_key.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Matches any invalid keys from runtime fields such as runtime fields which can start with a + * "." or runtime fields which can have ".." two or more dots. + * @param fieldsKey The fields key to match against + * @returns true if it is invalid key, otherwise false + */ +export const isInvalidKey = (fieldsKey: string): boolean => { + return fieldsKey.startsWith('.') || fieldsKey.match(/[\.]{2,}/) != null; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.test.ts new file mode 100644 index 0000000000000..a8050b600b464 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isMultiField } from './is_multifield'; + +describe('is_multifield', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + const dummyValue = ['value']; + + test('it returns true if the string "foo.bar" is a multiField', () => { + expect(isMultiField('foo.bar', [['foo', dummyValue]])).toEqual(true); + }); + + test('it returns false if the string "foo" is not a multiField', () => { + expect(isMultiField('foo', [['foo', dummyValue]])).toEqual(false); + }); + + test('it returns false if we have a sibling string and are not a multiField', () => { + expect(isMultiField('foo.bar', [['foo.mar', dummyValue]])).toEqual(false); + }); + + test('it returns true for a 3rd level match of being a sub-object. Runtime fields can have multiple layers of multiFields', () => { + expect(isMultiField('foo.mars.bar', [['foo', dummyValue]])).toEqual(true); + }); + + test('it returns true for a 3rd level match against a 2nd level sub-object. Runtime fields can have multiple layers of multiFields', () => { + expect(isMultiField('foo.mars.bar', [['foo.mars', dummyValue]])).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.ts new file mode 100644 index 0000000000000..feee6026c60b3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_multifield.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FieldsType } from '../types'; + +/** + * Returns true if we are a multiField when passed in a fields entry and a fields key, + * otherwise false. Notice that runtime fields can have multiple levels of multiFields which is kind a problem + * but we compensate and test for that here as well. So technically this matches both multiFields and + * invalid multiple-multiFields. + * @param fieldsKey The key to check against the entries to see if it is a multiField + * @param fieldEntries The entries to check against. + * @returns True if we are a subObject, otherwise false. + */ +export const isMultiField = ( + fieldsKey: string, + fieldEntries: Array<[string, FieldsType]> +): boolean => { + const splitPath = fieldsKey.split('.'); + return splitPath.some((_, index, array) => { + if (index + 1 === array.length) { + return false; + } else { + const newPath = [...array].splice(0, index + 1).join('.'); + return fieldEntries.some(([fieldKeyToCheck]) => { + return fieldKeyToCheck === newPath; + }); + } + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.test.ts new file mode 100644 index 0000000000000..d083fb8bdf845 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isNestedObject } from './is_nested_object'; + +describe('is_nested_object', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns false when an empty array is passed in', () => { + expect(isNestedObject([])).toEqual(false); + }); + + /** + * Simple table test of values of primitive arrays which should all return false + */ + test.each([ + [[1]], + [['string']], + [[true]], + [[String('hi')]], + [[Number(5)]], + [[{ type: 'point' }]], + ])('returns false when a primitive array of %o is passed in', (arrayValues) => { + expect(isNestedObject(arrayValues)).toEqual(false); + }); + + /** + * Simple table test of values of primitive arrays which should all return true + */ + test.each([[[{}]], [[{ a: 'foo' }]]])( + 'returns false when a primitive array of %o is passed in', + (arrayValues) => { + expect(isNestedObject(arrayValues)).toEqual(true); + } + ); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.ts new file mode 100644 index 0000000000000..38a0f871279eb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_nested_object.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isObjectLike } from 'lodash/fp'; +import { isTypeObject } from './is_type_object'; +import { FieldsType } from '../types'; + +/** + * Returns true if the first value is object-like but does not contain the shape of + * a "type object" such as geo point, then it makes an assumption everything is objectlike + * and not "type object" for all the array values. This should be used only for checking + * for nested object types within fields. + * @param fieldsValue The value to check if the first element is object like or not + * @returns True if this is a nested object, otherwise false. + */ +export const isNestedObject = (fieldsValue: FieldsType): boolean => { + return isObjectLike(fieldsValue[0]) && !isTypeObject(fieldsValue); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.test.ts new file mode 100644 index 0000000000000..454b70e69d0a8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isObjectLikeOrArrayOfObjectLikes } from './is_objectlike_or_array_of_objectlikes'; + +describe('is_objectlike_or_array_of_objectlikes', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns false when an empty array is passed in', () => { + expect(isObjectLikeOrArrayOfObjectLikes([])).toEqual(false); + }); + + test('returns false when an array of primitives are passed in', () => { + expect(isObjectLikeOrArrayOfObjectLikes([null, 2, 'string', 5, undefined])).toEqual(false); + }); + + /** + * Simple table test of values of primitive arrays which should all fail + */ + test.each([ + [[null]], + [[1]], + [['string']], + [['string', null, 5, false, String('hi'), Boolean(true), undefined]], + ])('returns true when a primitive array of %o is passed in', (arrayValue) => { + expect(isObjectLikeOrArrayOfObjectLikes(arrayValue)).toEqual(false); + }); + + /** + * Simple table test of values of primitives which should all fail + */ + test.each([[null], [1], ['string'], [null], [String('hi')], [Boolean(true)], [undefined]])( + 'returns true when a primitive array of %o is passed in', + (arrayValue) => { + expect(isObjectLikeOrArrayOfObjectLikes(arrayValue)).toEqual(false); + } + ); + + /** + * Simple table test of values of all array of objects which should pass + */ + test.each([ + [[{}]], + [[{ a: 1 }]], + [[1, {}]], + [[[], 'string']], + [['string', null, 5, false, String('hi'), {}, Boolean(true), undefined]], + ])('returns false when the array of %o contains an object is passed in', (arrayValue) => { + expect(isObjectLikeOrArrayOfObjectLikes(arrayValue)).toEqual(true); + }); + + /** + * Simple table test of objects which should pass + */ + test.each([[{}], [{ a: 1 }]])( + 'returns false when the array of %o contains an object is passed in', + (arrayValue) => { + expect(isObjectLikeOrArrayOfObjectLikes(arrayValue)).toEqual(true); + } + ); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts new file mode 100644 index 0000000000000..3f57eda31ca3a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_objectlike_or_array_of_objectlikes.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isObjectLike } from 'lodash/fp'; +import { SearchTypes } from '../../../../../../common/detection_engine/types'; + +/** + * Returns true if at least one element is an object, otherwise false if they all are not objects + * if this is an array. If it is not an array, this will check that single type + * @param valueInMergedDocument The search type to check if it is object like or not + * @returns true if is object like and not an array, or true if it is an array and at least 1 element is object like + */ +export const isObjectLikeOrArrayOfObjectLikes = ( + valueInMergedDocument: SearchTypes | null +): boolean => { + if (Array.isArray(valueInMergedDocument)) { + return valueInMergedDocument.some((value) => isObjectLike(value)); + } else { + return isObjectLike(valueInMergedDocument); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.test.ts new file mode 100644 index 0000000000000..6d1b273df4ad4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.test.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPrimitive } from './is_primitive'; + +describe('is_primitives', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns false when an empty array is passed in', () => { + expect(isPrimitive([])).toEqual(false); + }); + + /** + * Simple table test of values of primitive values which should all pass + */ + test.each([[null], [1], ['string'], [true], [Boolean('true')]])( + 'returns true when a primitive array of %o is passed in', + (arrayValue) => { + expect(isPrimitive(arrayValue)).toEqual(true); + } + ); + + /** + * Simple table test of values of objects which should not pass + */ + test.each([[{}], [{ a: 1 }]])( + 'returns false when the array of %o contains an object is passed in', + (arrayValue) => { + expect(isPrimitive(arrayValue)).toEqual(false); + } + ); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.ts new file mode 100644 index 0000000000000..c74b5f085989b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_primitive.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isObjectLike } from 'lodash/fp'; +import { SearchTypes } from '../../../../../../common/detection_engine/types'; + +/** + * Returns true if it is a primitive type, otherwise false + */ +export const isPrimitive = (valueInMergedDocument: SearchTypes | null): boolean => { + return !isObjectLike(valueInMergedDocument); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.test.ts new file mode 100644 index 0000000000000..ee5ba60e91e1a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isTypeObject } from './is_type_object'; + +describe('is_type_object', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('returns false when an empty array is passed in', () => { + expect(isTypeObject([])).toEqual(false); + }); + + test('returns true when a type object is in the array', () => { + expect(isTypeObject([{ type: 'Point' }])).toEqual(true); + }); + + test('returns false when a type object is not in the array', () => { + expect(isTypeObject([{ foo: 'a' }])).toEqual(false); + }); + + test('returns false when a primitive is passed in', () => { + expect(isTypeObject(['string'])).toEqual(false); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.ts new file mode 100644 index 0000000000000..68afad9ff4fe3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/is_type_object.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash/fp'; +import { FieldsType } from '../types'; + +/** + * Returns true if we match a "type" object which could be a geo-point when we are parsing field + * values and we encounter a geo-point. + * @param fieldsValue The value to test the shape of the data and see if it is a geo-point or not + * @returns True if we match a geo-point or another type or not. + */ +export const isTypeObject = (fieldsValue: FieldsType): boolean => { + return (fieldsValue as Array).some((value) => { + if (typeof value === 'object' && value != null) { + return get('type', value); + } else { + return false; + } + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.test.ts new file mode 100644 index 0000000000000..130990393b743 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.test.ts @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SearchTypes } from '../../../../../../common/detection_engine/types'; +import { recursiveUnboxingFields } from './recursive_unboxing_fields'; +import { FieldsType } from '../types'; + +describe('recursive_unboxing_fields', () => { + beforeAll(() => { + jest.resetAllMocks(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('valueInMergedDocument is "undefined"', () => { + const valueInMergedDocument: SearchTypes = undefined; + test('it will return an empty array as is', () => { + const nested: FieldsType = []; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual([]); + }); + + test('it will return an empty object as is', () => { + const nested: FieldsType[0] = {}; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual({}); + }); + + test('it will unbox a single array field', () => { + const nested: FieldsType = ['foo_value_1']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual('foo_value_1'); + }); + + test('it will not unbox an array with two fields', () => { + const nested: FieldsType = ['foo_value_1', 'foo_value_2']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual([ + 'foo_value_1', + 'foo_value_2', + ]); + }); + + test('it will unbox a nested structure of 3 single arrays', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual({ bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }); + }); + + test('it will not unbox a nested structure of 2 array values at the top most level', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([ + { bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }, + { bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }, + ]); + }); + + test('it will not unbox a nested structure of mixed values at different levels', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + fred: { + yolo: ['deep_1', 'deep_2'], + }, + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual({ + bar: { fred: { yolo: ['deep_1', 'deep_2'] }, zed: 'zed_value_1' }, + foo: 'foo_value_1', + }); + }); + }); + + describe('valueInMergedDocument is an empty object', () => { + const valueInMergedDocument: SearchTypes = {}; + test('it will return an empty array as is', () => { + const nested: FieldsType = []; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual([]); + }); + + test('it will return an empty object as is', () => { + const nested: FieldsType[0] = {}; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual({}); + }); + + test('it will unbox a single array field', () => { + const nested: FieldsType = ['foo_value_1']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual('foo_value_1'); + }); + + test('it will not unbox an array with two fields', () => { + const nested: FieldsType = ['foo_value_1', 'foo_value_2']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual([ + 'foo_value_1', + 'foo_value_2', + ]); + }); + + test('it will unbox a nested structure of 3 single arrays', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual({ bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }); + }); + + test('it will not unbox a nested structure of 2 array values at the top most level', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([ + { bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }, + { bar: { zed: 'zed_value_1' }, foo: 'foo_value_1' }, + ]); + }); + + test('it will not unbox a nested structure of mixed values at different levels', () => { + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + fred: { + yolo: ['deep_1', 'deep_2'], + }, + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual({ + bar: { fred: { yolo: ['deep_1', 'deep_2'] }, zed: 'zed_value_1' }, + foo: 'foo_value_1', + }); + }); + }); + + describe('valueInMergedDocument mirrors the nested field in different ways', () => { + test('it will not unbox when the valueInMergedDocument is an array value', () => { + const valueInMergedDocument: SearchTypes = ['foo_value_1']; + const nested: FieldsType = ['foo_value_1']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual(['foo_value_1']); + }); + + test('it will not unbox when the valueInMergedDocument is an empty array value', () => { + const valueInMergedDocument: SearchTypes = []; + const nested: FieldsType = ['foo_value_1']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual(['foo_value_1']); + }); + + test('it will not unbox an array with two fields', () => { + const valueInMergedDocument: SearchTypes = ['foo_value_1', 'foo_value_2']; + const nested: FieldsType = ['foo_value_1', 'foo_value_2']; + expect(recursiveUnboxingFields(nested, valueInMergedDocument)).toEqual([ + 'foo_value_1', + 'foo_value_2', + ]); + }); + + test('it will not unbox a nested structure of 3 single arrays when valueInMergedDocument has empty array values', () => { + const valueInMergedDocument: SearchTypes = [ + { + foo: [], + bar: { + zed: [], + }, + }, + ]; + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([{ bar: { zed: ['zed_value_1'] }, foo: ['foo_value_1'] }]); + }); + + test('it will not unbox a nested structure of 3 single arrays when valueInMergedDocument has array values', () => { + const valueInMergedDocument: SearchTypes = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([{ bar: { zed: ['zed_value_1'] }, foo: ['foo_value_1'] }]); + }); + + test('it will not overwrite a nested structure of 3 single arrays when valueInMergedDocument has array values that are different', () => { + const valueInMergedDocument: SearchTypes = [ + { + foo: ['other_value_1'], + bar: { + zed: ['other_value_2'], + }, + }, + ]; + const nested: FieldsType = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([{ bar: { zed: ['zed_value_1'] }, foo: ['foo_value_1'] }]); + }); + + test('it will work with mixed array values between "nested" and "valueInMergedDocument"', () => { + const valueInMergedDocument: SearchTypes = [ + { + foo: ['foo_value_1'], + bar: { + zed: ['zed_value_1'], + }, + }, + ]; + const nested: FieldsType = [ + { + foo: ['foo_value_1', 'foo_value_2', 'foo_value_3'], + bar: { + zed: ['zed_value_1', 'zed_value_1', 'zed_value_2'], + }, + }, + ]; + const recursed = recursiveUnboxingFields(nested, valueInMergedDocument); + expect(recursed).toEqual([ + { + bar: { zed: ['zed_value_1', 'zed_value_1', 'zed_value_2'] }, + foo: ['foo_value_1', 'foo_value_2', 'foo_value_3'], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts new file mode 100644 index 0000000000000..9cd0ebcb5a427 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash/fp'; +import { set } from '@elastic/safer-lodash-set/fp'; +import { SearchTypes } from '../../../../../../common/detection_engine/types'; +import { FieldsType } from '../types'; + +/** + * Recursively unboxes fields from an array when it is common sense to unbox them and safe to + * make an assumption to unbox them when we compare them to the "fieldsValue" and the "valueInMergedDocument" + * + * NOTE: We use "typeof fieldsValue === 'object' && fieldsValue != null" instead of lodash "objectLike" + * so that we can do type narrowing into an object to get the keys from it. + * + * @param fieldsValue The fields value that contains the nested field or not. + * @param valueInMergedDocument The document to compare against fields value to see if it is also an array or not + * @returns + */ +export const recursiveUnboxingFields = ( + fieldsValue: FieldsType | FieldsType[0], + valueInMergedDocument: SearchTypes +): FieldsType | FieldsType[0] => { + if (Array.isArray(fieldsValue)) { + const fieldsValueMapped = (fieldsValue as Array).map( + (value, index) => { + if (Array.isArray(valueInMergedDocument)) { + return recursiveUnboxingFields(value, valueInMergedDocument[index]); + } else { + return recursiveUnboxingFields(value, undefined); + } + } + ); + + if (fieldsValueMapped.length === 1) { + if (Array.isArray(valueInMergedDocument)) { + return fieldsValueMapped; + } else { + return fieldsValueMapped[0]; + } + } else { + return fieldsValueMapped; + } + } else if (typeof fieldsValue === 'object' && fieldsValue != null) { + const reducedFromKeys = Object.keys(fieldsValue).reduce((accum, key) => { + const recursed = recursiveUnboxingFields( + get(key, fieldsValue), + get(key, valueInMergedDocument) + ); + return set(key, recursed, accum); + }, {}); + return reducedFromKeys; + } else { + return fieldsValue; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index c399454b9888b..3fc36d5930a0a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -83,14 +83,25 @@ export interface RuleRangeTuple { maxSignals: number; } +/** + * SignalSource is being used as both a type for documents that match detection engine queries as well as + * for queries that could be on top of signals. In cases where it is matched against detection engine queries, + * '@timestamp' might not be there since it is not required and we have timestamp override capabilities. Also + * the signal addition object, "signal?: {" will not be there unless it's a conflicting field when we are running + * queries on events. + * + * For cases where we are running queries against signals (signals on signals) "@timestamp" should always be there + * and the "signal?: {" sub-object should always be there. + */ export interface SignalSource { [key: string]: SearchTypes; - // TODO: SignalSource is being used as the type for documents matching detection engine queries, but they may not - // actually have @timestamp if a timestamp override is used - '@timestamp': string; + '@timestamp'?: string; signal?: { - // parent is deprecated: new signals should populate parents instead - // both are optional until all signals with parent are gone and we can safely remove it + /** + * "parent" is deprecated: new signals should populate "parents" instead. Both are optional + * until all signals with parent are gone and we can safely remove it. + * @deprecated Use parents instead + */ parent?: Ancestor; parents?: Ancestor[]; ancestors: Ancestor[]; @@ -101,7 +112,7 @@ export interface SignalSource { rule: { id: string; }; - // signal.depth doesn't exist on pre-7.10 signals + /** signal.depth was introduced in 7.10 and pre-7.10 signals do not have it. */ depth?: number; original_time?: string; threshold_result?: ThresholdResult; @@ -202,7 +213,9 @@ export interface Signal { version: number; }; rule: RulesSchema; - // DEPRECATED: use parents instead of parent + /** + * @deprecated Use "parents" instead of "parent" + */ parent?: Ancestor; parents: Ancestor[]; ancestors: Ancestor[]; 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 616cf714d6a8c..4d5ac05957a4b 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 @@ -1368,6 +1368,45 @@ describe('utils', () => { const date = getValidDateFromDoc({ doc, timestampOverride: 'different_timestamp' }); expect(date?.toISOString()).toEqual(override); }); + + test('It returns the timestamp if the timestamp happens to be a string of an epoch when it has it in _source and fields', () => { + const doc = sampleDocNoSortId(); + const testDateString = '2021-06-25T15:53:56.590Z'; + const testDate = `${new Date(testDateString).valueOf()}`; + doc._source['@timestamp'] = testDate; + if (doc.fields != null) { + doc.fields['@timestamp'] = [testDate]; + } + const date = getValidDateFromDoc({ doc, timestampOverride: undefined }); + expect(date?.toISOString()).toEqual(testDateString); + }); + + test('It returns the timestamp if the timestamp happens to be a string of an epoch when it has it in _source and fields is nonexistent', () => { + const doc = sampleDocNoSortId(); + const testDateString = '2021-06-25T15:53:56.590Z'; + const testDate = `${new Date(testDateString).valueOf()}`; + doc._source['@timestamp'] = testDate; + doc.fields = undefined; + const date = getValidDateFromDoc({ doc, timestampOverride: undefined }); + expect(date?.toISOString()).toEqual(testDateString); + }); + + test('It returns the timestamp if the timestamp happens to be a string of an epoch in an override field', () => { + const override = '2020-10-07T19:36:31.110Z'; + const testDate = `${new Date(override).valueOf()}`; + let doc = sampleDocNoSortId(); + if (doc == null) { + throw new TypeError('Test requires one element'); + } + doc = { + ...doc, + fields: { + different_timestamp: [testDate], + }, + }; + const date = getValidDateFromDoc({ doc, timestampOverride: 'different_timestamp' }); + expect(date?.toISOString()).toEqual(override); + }); }); describe('createSearchAfterReturnType', () => { 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 6d67bab6eb2f7..4dd434156288f 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 @@ -625,6 +625,15 @@ export const getValidDateFromDoc = ({ const tempMoment = moment(lastTimestamp); if (tempMoment.isValid()) { return tempMoment.toDate(); + } else if (typeof timestampValue === 'string') { + // worse case we have a string from fields API or other areas of Elasticsearch that have given us a number as a string, + // so we try one last time to parse this best we can by converting from string to a number + const maybeDate = moment(+lastTimestamp); + if (maybeDate.isValid()) { + return maybeDate.toDate(); + } else { + return undefined; + } } else { return undefined; } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts index ca1281e0d2da9..790dc2b725a72 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/aliases.ts @@ -40,7 +40,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should keep the original alias value such as "host_alias" from a source index when the value is indexed', async () => { - const rule = getRuleForSignalTesting(['alias']); + const rule = getRuleForSignalTesting(['host_alias']); const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); @@ -51,9 +51,8 @@ export default ({ getService }: FtrProviderContext) => { expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); }); - // TODO: Make aliases work to where we can have ECS fields such as host.name filled out - it.skip('should copy alias data from a source index into the signals index in the same position when the target is ECS compatible', async () => { - const rule = getRuleForSignalTesting(['alias']); + it('should copy alias data from a source index into the signals index in the same position when the target is ECS compatible', async () => { + const rule = getRuleForSignalTesting(['host_alias']); const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts index 2294d51537fb1..6a6822ba7eb2d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_ml.ts @@ -191,6 +191,13 @@ export default ({ getService }: FtrProviderContext) => { }, original_time: '2020-11-16T22:58:08.000Z', }, + all_field_values: [ + 'store', + 'linux_anomalous_network_activity_ecs', + 'root', + 'store', + 'mothra', + ], }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts index fccfe4d74e241..b793fc635843e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/const_keyword.ts @@ -60,8 +60,7 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).to.eql(4); }); - // TODO: Fix this bug and make this work. We currently do not write out the dataset name when it is not in _source - it.skip('should copy the dataset_name_1 from the index into the signal', async () => { + it('should copy the dataset_name_1 from the index into the signal', async () => { const rule = { ...getRuleForSignalTesting(['const_keyword']), query: 'event.dataset: "dataset_name_1"', @@ -99,8 +98,7 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).to.eql(4); }); - // TODO: Fix this bug and make this work. We currently do not write out the dataset name when it is not in _source - it.skip('should copy the "dataset_name_1" from "event.dataset"', async () => { + it('should copy the "dataset_name_1" from "event.dataset"', async () => { const rule: EqlCreateSchema = { ...getRuleForSignalTesting(['const_keyword']), rule_id: 'eql-rule', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts index 3802d1f7a7bef..2ce88da13afab 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/keyword_family/keyword_mixed_with_const.ts @@ -62,8 +62,7 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).to.eql(8); }); - // TODO: Fix this bug and make this work. We currently do not write out the dataset name when it is not in _source - it.skip('should copy the dataset_name_1 from the index into the signal', async () => { + it('should copy the dataset_name_1 from the index into the signal', async () => { const rule = { ...getRuleForSignalTesting(['keyword', 'const_keyword']), query: 'event.dataset: "dataset_name_1"', @@ -105,8 +104,7 @@ export default ({ getService }: FtrProviderContext) => { expect(signalsOpen.hits.hits.length).to.eql(8); }); - // TODO: Fix this bug and make this work. We currently do not write out the dataset name when it is not in _source - it.skip('should copy the "dataset_name_1" from "event.dataset"', async () => { + it('should copy the "dataset_name_1" from "event.dataset"', async () => { const rule: EqlCreateSchema = { ...getRuleForSignalTesting(['keyword', 'const_keyword']), rule_id: 'eql-rule', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts index 94cc390e0e6ef..0015a41f911d4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/runtime.ts @@ -23,7 +23,7 @@ import { export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - interface HostAlias { + interface Runtime { name: string; hostname: string; } @@ -47,19 +47,18 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); - const hits = signalsOpen.hits.hits.map((signal) => (signal._source.host as HostAlias).name); + const hits = signalsOpen.hits.hits.map((signal) => (signal._source.host as Runtime).name); expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); }); - // TODO: Make runtime fields able to be copied to where we can have ECS fields such as host.name filled out by them within the mapping directly - it.skip('should copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { + it('should copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime']); const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map( - (signal) => (signal._source.host_alias as HostAlias).hostname + (signal) => (signal._source.host as Runtime).hostname ); expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); }); @@ -81,33 +80,69 @@ export default ({ getService }: FtrProviderContext) => { ); }); - // TODO: Make the overrides of runtime fields override the host.name in this use case. - it.skip('should copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => { + /** + * Note, this test shows that we do not shadow or overwrite runtime fields on-top of regular fields as we reduced + * risk with overwriting fields in the strategy we are currently using in detection engine. If you swap, change the strategies + * because we decide to overwrite "_source" values with "fields", then expect to change this test. + */ + it('should NOT copy normal non-runtime data set from the source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime_conflicting_fields']); const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); - const hits = signalsOpen.hits.hits.map((signal) => (signal._source.host as HostAlias).name); + const hits = signalsOpen.hits.hits.map((signal) => signal._source.host); expect(hits).to.eql([ - 'I am the [host.name] field value which shadows the original host.name value', - 'I am the [host.name] field value which shadows the original host.name value', - 'I am the [host.name] field value which shadows the original host.name value', - 'I am the [host.name] field value which shadows the original host.name value', + [ + { + name: 'host name 1_1', + }, + { + name: 'host name 1_2', + }, + ], + [ + { + name: 'host name 2_1', + }, + { + name: 'host name 2_2', + }, + ], + [ + { + name: 'host name 3_1', + }, + { + name: 'host name 3_2', + }, + ], + [ + { + name: 'host name 4_1', + }, + { + name: 'host name 4_2', + }, + ], ]); }); - // TODO: Make runtime fields able to be copied to where we can have ECS fields such as host.name filled out by them within the mapping directly - it.skip('should copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { + /** + * Note, this test shows that we do NOT shadow or overwrite runtime fields on-top of regular fields when we detect those + * fields as arrays of objects since the objects are flattened in "fields" and we detect something already there so we skip + * this shadowed runtime data as it is ambiguous of where we would put it in the array. + */ + it('should NOT copy "runtime mapping" data from a source index into the signals index in the same position when the target is ECS compatible', async () => { const rule = getRuleForSignalTesting(['runtime_conflicting_fields']); const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); await waitForSignalsToBePresent(supertest, 4, [id]); const signalsOpen = await getSignalsById(supertest, id); const hits = signalsOpen.hits.hits.map( - (signal) => (signal._source.host_alias as HostAlias).hostname + (signal) => (signal._source.host as Runtime).hostname ); - expect(hits).to.eql(['host name 1', 'host name 2', 'host name 3', 'host name 4']); + expect(hits).to.eql([undefined, undefined, undefined, undefined]); }); }); }); diff --git a/x-pack/test/functional/es_archives/security_solution/alias/data.json b/x-pack/test/functional/es_archives/security_solution/alias/data.json index a8bd64cb044eb..f0f74a9094df1 100644 --- a/x-pack/test/functional/es_archives/security_solution/alias/data.json +++ b/x-pack/test/functional/es_archives/security_solution/alias/data.json @@ -2,7 +2,7 @@ "type": "doc", "value": { "id": "1", - "index": "alias", + "index": "host_alias", "source": { "@timestamp": "2020-10-28T05:00:53.000Z", "host_alias": { @@ -17,7 +17,7 @@ "type": "doc", "value": { "id": "2", - "index": "alias", + "index": "host_alias", "source": { "@timestamp": "2020-10-28T05:01:53.000Z", "host_alias": { @@ -32,7 +32,7 @@ "type": "doc", "value": { "id": "3", - "index": "alias", + "index": "host_alias", "source": { "@timestamp": "2020-10-28T05:02:53.000Z", "host_alias": { @@ -47,7 +47,7 @@ "type": "doc", "value": { "id": "4", - "index": "alias", + "index": "host_alias", "source": { "@timestamp": "2020-10-28T05:03:53.000Z", "host_alias": { diff --git a/x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields/mappings.json b/x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields/mappings.json index 04a538a332953..2e34eae159a7f 100644 --- a/x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/runtime_conflicting_fields/mappings.json @@ -5,7 +5,7 @@ "mappings": { "dynamic": "strict", "runtime": { - "host_alias": { + "host.hostname": { "type": "keyword", "script": { "source": "emit(doc['host.name'].value)" @@ -98,6 +98,9 @@ "properties": { "name": { "type": "keyword" + }, + "hostname": { + "type": "keyword" } } } From f28bfa71ad1ef9803d9a5fa59fcbe70c50564373 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 28 Jun 2021 18:13:12 -0600 Subject: [PATCH 35/74] [Maps] Move edit tools to beta and remove experimental config flags (#103556) --- x-pack/plugins/maps/config.ts | 2 -- .../public/classes/layers/layer_wizard_registry.ts | 2 ++ .../public/classes/layers/load_layer_wizards.ts | 5 +---- .../layers/new_vector_layer_wizard/config.tsx | 4 +++- .../sources/es_search_source/es_search_source.tsx | 10 +--------- .../flyout_body/layer_wizard_select.tsx | 1 + x-pack/plugins/maps/server/index.ts | 1 - x-pack/plugins/maps/server/plugin.ts | 9 +-------- x-pack/plugins/maps/server/routes.js | 13 ++----------- x-pack/test/functional/config.js | 1 - 10 files changed, 11 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/maps/config.ts b/x-pack/plugins/maps/config.ts index 781967714be03..104ba00263545 100644 --- a/x-pack/plugins/maps/config.ts +++ b/x-pack/plugins/maps/config.ts @@ -10,7 +10,6 @@ import { schema, TypeOf } from '@kbn/config-schema'; export interface MapsConfigType { enabled: boolean; showMapVisualizationTypes: boolean; - enableDrawingFeature: boolean; showMapsInspectorAdapter: boolean; preserveDrawingBuffer: boolean; } @@ -18,7 +17,6 @@ export interface MapsConfigType { export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), showMapVisualizationTypes: schema.boolean({ defaultValue: false }), - enableDrawingFeature: schema.boolean({ defaultValue: false }), // flag used in functional testing showMapsInspectorAdapter: schema.boolean({ defaultValue: false }), // flag used in functional testing diff --git a/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts index 824d9835380ec..b14ba7cf693b0 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer_wizard_registry.ts @@ -33,6 +33,7 @@ export type LayerWizard = { description: string; disabledReason?: string; getIsDisabled?: () => Promise | boolean; + isBeta?: boolean; icon: string | FunctionComponent; prerequisiteSteps?: Array<{ id: string; label: string }>; renderWizard(renderWizardArguments: RenderWizardArguments): ReactElement; @@ -54,6 +55,7 @@ export function registerLayerWizard(layerWizard: LayerWizard) { getIsDisabled: async () => { return false; }, + isBeta: false, ...layerWizard, }); } diff --git a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts index 2c7f09ce9dfeb..3c86c57343a06 100644 --- a/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/classes/layers/load_layer_wizards.ts @@ -29,7 +29,6 @@ import { ObservabilityLayerWizardConfig } from './solution_layers/observability' import { SecurityLayerWizardConfig } from './solution_layers/security'; import { choroplethLayerWizardConfig } from './choropleth_layer_wizard'; import { newVectorLayerWizardConfig } from './new_vector_layer_wizard'; -import { getMapAppConfig } from '../../kibana_services'; let registered = false; export function registerLayerWizards() { @@ -39,9 +38,7 @@ export function registerLayerWizards() { // Registration order determines display order registerLayerWizard(uploadLayerWizardConfig); - if (getMapAppConfig().enableDrawingFeature) { - registerLayerWizard(newVectorLayerWizardConfig); - } + registerLayerWizard(newVectorLayerWizardConfig); registerLayerWizard(esDocumentsLayerWizardConfig); // @ts-ignore registerLayerWizard(choroplethLayerWizardConfig); diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx index 2a0400c3d6bee..c4464c8787c19 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx @@ -18,7 +18,8 @@ const ADD_VECTOR_DRAWING_LAYER = 'ADD_VECTOR_DRAWING_LAYER'; export const newVectorLayerWizardConfig: LayerWizard = { categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH], description: i18n.translate('xpack.maps.newVectorLayerWizard.description', { - defaultMessage: 'Creates a new empty layer. Use this to add shapes to the map', + defaultMessage: + 'Create an empty layer. Use this to create documents by drawing shapes on the map', }), disabledReason: i18n.translate('xpack.maps.newVectorLayerWizard.disabledDesc', { defaultMessage: @@ -31,6 +32,7 @@ export const newVectorLayerWizardConfig: LayerWizard = { }); return !hasImportPermission; }, + isBeta: true, icon: DrawLayerIcon, prerequisiteSteps: [ { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 019c3c1b4943b..343c366b548f6 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -12,12 +12,7 @@ import { i18n } from '@kbn/i18n'; import { IFieldType, IndexPattern } from 'src/plugins/data/public'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { AbstractESSource } from '../es_source'; -import { - getHttp, - getMapAppConfig, - getSearchService, - getTimeFilter, -} from '../../../kibana_services'; +import { getHttp, getSearchService, getTimeFilter } from '../../../kibana_services'; import { addFieldToDSL, getField, @@ -424,9 +419,6 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye } async supportsFeatureEditing(): Promise { - if (!getMapAppConfig().enableDrawingFeature) { - return false; - } await this.getIndexPattern(); if (!(this.indexPattern && this.indexPattern.title)) { return false; diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx index 5350b0e2ccf38..1e92dd16d9dac 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/flyout_body/layer_wizard_select.tsx @@ -161,6 +161,7 @@ export class LayerWizardSelect extends Component { = { exposeToBrowser: { enabled: true, showMapVisualizationTypes: true, - enableDrawingFeature: true, showMapsInspectorAdapter: true, preserveDrawingBuffer: true, }, diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts index b8676559a4e2b..c5fc602864f96 100644 --- a/x-pack/plugins/maps/server/plugin.ts +++ b/x-pack/plugins/maps/server/plugin.ts @@ -170,14 +170,7 @@ export class MapsPlugin implements Plugin { lastLicenseId = license.uid; }); - initRoutes( - core, - () => lastLicenseId, - emsSettings, - this.kibanaVersion, - this._logger, - currentConfig.enableDrawingFeature - ); + initRoutes(core, () => lastLicenseId, emsSettings, this.kibanaVersion, this._logger); this._initHomeData(home, core.http.basePath.prepend, emsSettings); diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 39ce9979870c5..1b669a5bcdbee 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -54,14 +54,7 @@ const EMPTY_EMS_CLIENT = { addQueryParams() {}, }; -export async function initRoutes( - core, - getLicenseId, - emsSettings, - kbnVersion, - logger, - drawingFeatureEnabled -) { +export async function initRoutes(core, getLicenseId, emsSettings, kbnVersion, logger) { let emsClient; let lastLicenseId; const router = core.http.createRouter(); @@ -624,7 +617,5 @@ export async function initRoutes( } initMVTRoutes({ router, logger }); - if (drawingFeatureEnabled) { - initIndexingRoutes({ router, logger, dataPlugin }); - } + initIndexingRoutes({ router, logger, dataPlugin }); } diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 2c3a3c93e2a0a..a79e51057c90e 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -85,7 +85,6 @@ export default async function ({ readConfigFile }) { '--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d', '--xpack.maps.showMapsInspectorAdapter=true', '--xpack.maps.preserveDrawingBuffer=true', - '--xpack.maps.enableDrawingFeature=true', '--xpack.reporting.roles.enabled=false', // use the non-deprecated access control model for Reporting '--xpack.reporting.queue.pollInterval=3000', // make it explicitly the default '--xpack.reporting.csv.maxSizeBytes=2850', // small-ish limit for cutting off a 1999 byte report From ad3601c260b7e7e683c7976a6734604cec04b39f Mon Sep 17 00:00:00 2001 From: fgierlinger <2966031+fgierlinger@users.noreply.github.com> Date: Tue, 29 Jun 2021 02:17:26 +0200 Subject: [PATCH 36/74] fix: typo in time dropdown list (#103407) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/node_details/tabs/metrics/time_dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/time_dropdown.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/time_dropdown.tsx index c080cf279478f..28e57992cfafb 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/time_dropdown.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/time_dropdown.tsx @@ -20,7 +20,7 @@ export const TimeDropdown = (props: Props) => ( options={[ { text: i18n.translate('xpack.infra.nodeDetails.metrics.last15Minutes', { - defaultMessage: 'Last 15 mintues', + defaultMessage: 'Last 15 minutes', }), value: 15 * 60 * 1000, }, From 699c875b210c2451959c6b7d41116c83d51284b9 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 28 Jun 2021 19:18:14 -0500 Subject: [PATCH 37/74] [Workplace Search] Fix edge case API error (#103574) This PR fixes an edge case where a race condition mught cause the total_results from a federated content source to come back null from the server. This PR tells the server to expect null in those edge cases to prevent browser errors --- .../enterprise_search/server/routes/workplace_search/sources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index 7e9d7d92742ab..044393f65dc59 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -25,7 +25,7 @@ const pageSchema = schema.object({ current: schema.nullable(schema.number()), size: schema.nullable(schema.number()), total_pages: schema.nullable(schema.number()), - total_results: schema.number(), + total_results: schema.nullable(schema.number()), }); const oauthConfigSchema = schema.object({ From aafcc473f3a947cc406fb3024767351f9c740ed7 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 28 Jun 2021 17:23:10 -0700 Subject: [PATCH 38/74] [Reporting] Reorganize UI components (#103571) --- x-pack/plugins/reporting/public/index.ts | 2 +- .../job_queue_client.test.mocks.ts | 2 +- .../reporting/public/lib/reporting_api_client.ts | 2 +- .../plugins/reporting/public/lib/stream_handler.ts | 2 +- .../__snapshots__/report_info_button.test.tsx.snap | 0 .../__snapshots__/report_listing.test.tsx.snap | 0 .../buttons/index.tsx => management/index.ts} | 0 .../{ => management}/mount_management_section.tsx | 14 +++++++------- .../report_delete_button.tsx | 6 +++--- .../report_diagnostic.tsx | 0 .../report_download_button.tsx | 4 ++-- .../buttons => management}/report_error_button.tsx | 6 +++--- .../report_info_button.test.tsx | 4 ++-- .../buttons => management}/report_info_button.tsx | 6 +++--- .../report_listing.test.tsx | 0 .../{components => management}/report_listing.tsx | 7 +------ x-pack/plugins/reporting/public/mocks.ts | 2 +- .../{components => notifier}/general_error.tsx | 0 .../public/{components => notifier}/index.ts | 6 ++---- .../job_completion_notifications.ts | 0 .../job_download_button.tsx | 0 .../{components => notifier}/job_failure.tsx | 0 .../{components => notifier}/job_success.tsx | 0 .../job_warning_formulas.tsx | 0 .../job_warning_max_size.tsx | 0 .../{components => notifier}/report_link.tsx | 0 x-pack/plugins/reporting/public/plugin.ts | 5 +++-- .../screen_capture_panel_content.test.tsx.snap | 0 .../panel_spinner.tsx | 0 .../share_context_menu/register_csv_reporting.tsx | 2 +- .../register_pdf_png_reporting.tsx | 2 +- .../reporting_panel_content.test.tsx | 0 .../reporting_panel_content.tsx | 0 .../reporting_panel_content_lazy.tsx | 2 +- .../screen_capture_panel_content.test.tsx | 0 .../screen_capture_panel_content.tsx | 0 .../screen_capture_panel_content_lazy.tsx | 2 +- .../shared/get_shared_components.tsx | 8 ++++---- .../shared/index.tsx => shared/index.ts} | 0 39 files changed, 39 insertions(+), 45 deletions(-) rename x-pack/plugins/reporting/public/{components => lib}/job_queue_client.test.mocks.ts (87%) rename x-pack/plugins/reporting/public/{components/buttons => management}/__snapshots__/report_info_button.test.tsx.snap (100%) rename x-pack/plugins/reporting/public/{components => management}/__snapshots__/report_listing.test.tsx.snap (100%) rename x-pack/plugins/reporting/public/{components/buttons/index.tsx => management/index.ts} (100%) rename x-pack/plugins/reporting/public/{ => management}/mount_management_section.tsx (78%) rename x-pack/plugins/reporting/public/{components/buttons => management}/report_delete_button.tsx (94%) rename x-pack/plugins/reporting/public/{components => management}/report_diagnostic.tsx (100%) rename x-pack/plugins/reporting/public/{components/buttons => management}/report_download_button.tsx (93%) rename x-pack/plugins/reporting/public/{components/buttons => management}/report_error_button.tsx (93%) rename x-pack/plugins/reporting/public/{components/buttons => management}/report_info_button.test.tsx (94%) rename x-pack/plugins/reporting/public/{components/buttons => management}/report_info_button.tsx (97%) rename x-pack/plugins/reporting/public/{components => management}/report_listing.test.tsx (100%) rename x-pack/plugins/reporting/public/{components => management}/report_listing.tsx (99%) rename x-pack/plugins/reporting/public/{components => notifier}/general_error.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/index.ts (81%) rename x-pack/plugins/reporting/public/{lib => notifier}/job_completion_notifications.ts (100%) rename x-pack/plugins/reporting/public/{components => notifier}/job_download_button.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/job_failure.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/job_success.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/job_warning_formulas.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/job_warning_max_size.tsx (100%) rename x-pack/plugins/reporting/public/{components => notifier}/report_link.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/__snapshots__/screen_capture_panel_content.test.tsx.snap (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/panel_spinner.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/reporting_panel_content.test.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/reporting_panel_content.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/reporting_panel_content_lazy.tsx (94%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/screen_capture_panel_content.test.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/screen_capture_panel_content.tsx (100%) rename x-pack/plugins/reporting/public/{components => share_context_menu}/screen_capture_panel_content_lazy.tsx (94%) rename x-pack/plugins/reporting/public/{components => }/shared/get_shared_components.tsx (79%) rename x-pack/plugins/reporting/public/{components/shared/index.tsx => shared/index.ts} (100%) diff --git a/x-pack/plugins/reporting/public/index.ts b/x-pack/plugins/reporting/public/index.ts index 7179a81664b6f..6acdd8fb048e8 100644 --- a/x-pack/plugins/reporting/public/index.ts +++ b/x-pack/plugins/reporting/public/index.ts @@ -7,9 +7,9 @@ import { PluginInitializerContext } from 'src/core/public'; import { getDefaultLayoutSelectors } from '../common'; -import { getSharedComponents } from './components'; import { ReportingAPIClient } from './lib/reporting_api_client'; import { ReportingPublicPlugin } from './plugin'; +import { getSharedComponents } from './shared'; export interface ReportingSetup { getDefaultLayoutSelectors: typeof getDefaultLayoutSelectors; diff --git a/x-pack/plugins/reporting/public/components/job_queue_client.test.mocks.ts b/x-pack/plugins/reporting/public/lib/job_queue_client.test.mocks.ts similarity index 87% rename from x-pack/plugins/reporting/public/components/job_queue_client.test.mocks.ts rename to x-pack/plugins/reporting/public/lib/job_queue_client.test.mocks.ts index 9426ec8d8751d..6c66eada6a825 100644 --- a/x-pack/plugins/reporting/public/components/job_queue_client.test.mocks.ts +++ b/x-pack/plugins/reporting/public/lib/job_queue_client.test.mocks.ts @@ -15,4 +15,4 @@ export const mockAPIClient = { downloadReport: jest.fn(), }; -jest.mock('../lib/reporting_api_client', () => mockAPIClient); +jest.mock('./reporting_api_client', () => mockAPIClient); diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client.ts index 92604de0f4712..4ce9e8760f21f 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client.ts @@ -22,7 +22,7 @@ import { ReportDocument, ReportSource, } from '../../common/types'; -import { add } from './job_completion_notifications'; +import { add } from '../notifier/job_completion_notifications'; export interface JobQueueEntry { _id: string; diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 961345aeb592c..53191cacb5ba1 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -17,7 +17,7 @@ import { getSuccessToast, getWarningFormulasToast, getWarningMaxSizeToast, -} from '../components'; +} from '../notifier'; import { ReportingAPIClient } from './reporting_api_client'; function updateStored(jobIds: JobId[]): void { diff --git a/x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap similarity index 100% rename from x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap rename to x-pack/plugins/reporting/public/management/__snapshots__/report_info_button.test.tsx.snap diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap similarity index 100% rename from x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap rename to x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap diff --git a/x-pack/plugins/reporting/public/components/buttons/index.tsx b/x-pack/plugins/reporting/public/management/index.ts similarity index 100% rename from x-pack/plugins/reporting/public/components/buttons/index.tsx rename to x-pack/plugins/reporting/public/management/index.ts diff --git a/x-pack/plugins/reporting/public/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx similarity index 78% rename from x-pack/plugins/reporting/public/mount_management_section.tsx rename to x-pack/plugins/reporting/public/management/mount_management_section.tsx index bc165badae713..eb1057a9bdfc7 100644 --- a/x-pack/plugins/reporting/public/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -5,16 +5,16 @@ * 2.0. */ +import { I18nProvider } from '@kbn/i18n/react'; import * as React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n/react'; -import { CoreSetup, CoreStart } from 'src/core/public'; import { Observable } from 'rxjs'; -import { ReportListing } from './components/report_listing'; -import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; -import { ILicense } from '../../licensing/public'; -import { ClientConfigType } from './plugin'; -import { ReportingAPIClient } from './lib/reporting_api_client'; +import { CoreSetup, CoreStart } from 'src/core/public'; +import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; +import { ILicense } from '../../../licensing/public'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ClientConfigType } from '../plugin'; +import { ReportListing } from './report_listing'; export async function mountManagementSection( coreSetup: CoreSetup, diff --git a/x-pack/plugins/reporting/public/components/buttons/report_delete_button.tsx b/x-pack/plugins/reporting/public/management/report_delete_button.tsx similarity index 94% rename from x-pack/plugins/reporting/public/components/buttons/report_delete_button.tsx rename to x-pack/plugins/reporting/public/management/report_delete_button.tsx index cd432758fa767..dfb411fc195e8 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_delete_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_delete_button.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { EuiConfirmModal, EuiButton } from '@elastic/eui'; -import React, { PureComponent, Fragment } from 'react'; -import { Job, Props as ListingProps } from '../report_listing'; +import { EuiButton, EuiConfirmModal } from '@elastic/eui'; +import React, { Fragment, PureComponent } from 'react'; +import { Job, Props as ListingProps } from './report_listing'; type DeleteFn = () => Promise; type Props = { jobsToDelete: Job[]; performDelete: DeleteFn } & ListingProps; diff --git a/x-pack/plugins/reporting/public/components/report_diagnostic.tsx b/x-pack/plugins/reporting/public/management/report_diagnostic.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/report_diagnostic.tsx rename to x-pack/plugins/reporting/public/management/report_diagnostic.tsx diff --git a/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx b/x-pack/plugins/reporting/public/management/report_download_button.tsx similarity index 93% rename from x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx rename to x-pack/plugins/reporting/public/management/report_download_button.tsx index a5e57dafc2867..78022b85e2ff8 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_download_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_download_button.tsx @@ -7,8 +7,8 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { FunctionComponent } from 'react'; -import { JOB_STATUSES } from '../../../common/constants'; -import { Job as ListingJob, Props as ListingProps } from '../report_listing'; +import { JOB_STATUSES } from '../../common/constants'; +import { Job as ListingJob, Props as ListingProps } from './report_listing'; type Props = { record: ListingJob } & ListingProps; diff --git a/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx b/x-pack/plugins/reporting/public/management/report_error_button.tsx similarity index 93% rename from x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx rename to x-pack/plugins/reporting/public/management/report_error_button.tsx index b34463b61253b..0ebdf5ca60b5a 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_error_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_error_button.tsx @@ -8,9 +8,9 @@ import { EuiButtonIcon, EuiCallOut, EuiPopover } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { JOB_STATUSES } from '../../../common/constants'; -import { JobContent, ReportingAPIClient } from '../../lib/reporting_api_client'; -import { Job as ListingJob } from '../report_listing'; +import { JOB_STATUSES } from '../../common/constants'; +import { JobContent, ReportingAPIClient } from '../lib/reporting_api_client'; +import { Job as ListingJob } from './report_listing'; interface Props { intl: InjectedIntl; diff --git a/x-pack/plugins/reporting/public/components/buttons/report_info_button.test.tsx b/x-pack/plugins/reporting/public/management/report_info_button.test.tsx similarity index 94% rename from x-pack/plugins/reporting/public/components/buttons/report_info_button.test.tsx rename to x-pack/plugins/reporting/public/management/report_info_button.test.tsx index 785f49646110a..119856042a326 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_info_button.test.tsx +++ b/x-pack/plugins/reporting/public/management/report_info_button.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test/jest'; import { ReportInfoButton } from './report_info_button'; -jest.mock('../../lib/reporting_api_client'); +jest.mock('../lib/reporting_api_client'); -import { ReportingAPIClient } from '../../lib/reporting_api_client'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; const httpSetup = {} as any; const apiClient = new ReportingAPIClient(httpSetup); diff --git a/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx b/x-pack/plugins/reporting/public/management/report_info_button.tsx similarity index 97% rename from x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx rename to x-pack/plugins/reporting/public/management/report_info_button.tsx index 7f2d5b6adcc33..719f1ff341daf 100644 --- a/x-pack/plugins/reporting/public/components/buttons/report_info_button.tsx +++ b/x-pack/plugins/reporting/public/management/report_info_button.tsx @@ -18,9 +18,9 @@ import { } from '@elastic/eui'; import { get } from 'lodash'; import React, { Component, Fragment } from 'react'; -import { USES_HEADLESS_JOB_TYPES } from '../../../common/constants'; -import { ReportApiJSON } from '../../../common/types'; -import { ReportingAPIClient } from '../../lib/reporting_api_client'; +import { USES_HEADLESS_JOB_TYPES } from '../../common/constants'; +import { ReportApiJSON } from '../../common/types'; +import { ReportingAPIClient } from '../lib/reporting_api_client'; interface Props { jobId: string; diff --git a/x-pack/plugins/reporting/public/components/report_listing.test.tsx b/x-pack/plugins/reporting/public/management/report_listing.test.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/report_listing.test.tsx rename to x-pack/plugins/reporting/public/management/report_listing.test.tsx diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx similarity index 99% rename from x-pack/plugins/reporting/public/components/report_listing.tsx rename to x-pack/plugins/reporting/public/management/report_listing.tsx index 618c91fba0715..fffa952be6cb4 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -28,12 +28,7 @@ import { durationToNumber } from '../../common/schema_utils'; import { checkLicense } from '../lib/license_check'; import { JobQueueEntry, ReportingAPIClient } from '../lib/reporting_api_client'; import { ClientConfigType } from '../plugin'; -import { - ReportDeleteButton, - ReportDownloadButton, - ReportErrorButton, - ReportInfoButton, -} from './buttons'; +import { ReportDeleteButton, ReportDownloadButton, ReportErrorButton, ReportInfoButton } from './'; import { ReportDiagnostic } from './report_diagnostic'; export interface Job { diff --git a/x-pack/plugins/reporting/public/mocks.ts b/x-pack/plugins/reporting/public/mocks.ts index 414d1b0ae70fe..a6b6d835499c6 100644 --- a/x-pack/plugins/reporting/public/mocks.ts +++ b/x-pack/plugins/reporting/public/mocks.ts @@ -8,7 +8,7 @@ import { coreMock } from 'src/core/public/mocks'; import { ReportingSetup } from '.'; import { getDefaultLayoutSelectors } from '../common'; -import { getSharedComponents } from './components/shared'; +import { getSharedComponents } from './shared'; type Setup = jest.Mocked; diff --git a/x-pack/plugins/reporting/public/components/general_error.tsx b/x-pack/plugins/reporting/public/notifier/general_error.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/general_error.tsx rename to x-pack/plugins/reporting/public/notifier/general_error.tsx diff --git a/x-pack/plugins/reporting/public/components/index.ts b/x-pack/plugins/reporting/public/notifier/index.ts similarity index 81% rename from x-pack/plugins/reporting/public/components/index.ts rename to x-pack/plugins/reporting/public/notifier/index.ts index b8cccda2a6613..b44f1e9169747 100644 --- a/x-pack/plugins/reporting/public/components/index.ts +++ b/x-pack/plugins/reporting/public/notifier/index.ts @@ -5,10 +5,8 @@ * 2.0. */ -export { getSuccessToast } from './job_success'; export { getFailureToast } from './job_failure'; +export { getGeneralErrorToast } from './general_error'; +export { getSuccessToast } from './job_success'; export { getWarningFormulasToast } from './job_warning_formulas'; export { getWarningMaxSizeToast } from './job_warning_max_size'; -export { getGeneralErrorToast } from './general_error'; -export { ScreenCapturePanelContent } from './screen_capture_panel_content'; -export { getSharedComponents } from './shared'; diff --git a/x-pack/plugins/reporting/public/lib/job_completion_notifications.ts b/x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts similarity index 100% rename from x-pack/plugins/reporting/public/lib/job_completion_notifications.ts rename to x-pack/plugins/reporting/public/notifier/job_completion_notifications.ts diff --git a/x-pack/plugins/reporting/public/components/job_download_button.tsx b/x-pack/plugins/reporting/public/notifier/job_download_button.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/job_download_button.tsx rename to x-pack/plugins/reporting/public/notifier/job_download_button.tsx diff --git a/x-pack/plugins/reporting/public/components/job_failure.tsx b/x-pack/plugins/reporting/public/notifier/job_failure.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/job_failure.tsx rename to x-pack/plugins/reporting/public/notifier/job_failure.tsx diff --git a/x-pack/plugins/reporting/public/components/job_success.tsx b/x-pack/plugins/reporting/public/notifier/job_success.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/job_success.tsx rename to x-pack/plugins/reporting/public/notifier/job_success.tsx diff --git a/x-pack/plugins/reporting/public/components/job_warning_formulas.tsx b/x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/job_warning_formulas.tsx rename to x-pack/plugins/reporting/public/notifier/job_warning_formulas.tsx diff --git a/x-pack/plugins/reporting/public/components/job_warning_max_size.tsx b/x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/job_warning_max_size.tsx rename to x-pack/plugins/reporting/public/notifier/job_warning_max_size.tsx diff --git a/x-pack/plugins/reporting/public/components/report_link.tsx b/x-pack/plugins/reporting/public/notifier/report_link.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/report_link.tsx rename to x-pack/plugins/reporting/public/notifier/report_link.tsx diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 577732fdb1392..a2881af902072 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -29,10 +29,11 @@ import { constants, getDefaultLayoutSelectors } from '../common'; import { durationToNumber } from '../common/schema_utils'; import { JobId, JobSummarySet } from '../common/types'; import { ReportingSetup, ReportingStart } from './'; -import { getGeneralErrorToast, getSharedComponents } from './components'; import { ReportingAPIClient } from './lib/reporting_api_client'; import { ReportingNotifierStreamHandler as StreamHandler } from './lib/stream_handler'; +import { getGeneralErrorToast } from './notifier'; import { ReportingCsvPanelAction } from './panel_actions/get_csv_panel_action'; +import { getSharedComponents } from './shared'; import { ReportingCsvShareProvider } from './share_context_menu/register_csv_reporting'; import { reportingScreenshotShareProvider } from './share_context_menu/register_pdf_png_reporting'; @@ -150,7 +151,7 @@ export class ReportingPublicPlugin params.setBreadcrumbs([{ text: this.breadcrumbText }]); const [[start], { mountManagementSection }] = await Promise.all([ getStartServices(), - import('./mount_management_section'), + import('./management/mount_management_section'), ]); return await mountManagementSection( core, diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap similarity index 100% rename from x-pack/plugins/reporting/public/components/__snapshots__/screen_capture_panel_content.test.tsx.snap rename to x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap diff --git a/x-pack/plugins/reporting/public/components/panel_spinner.tsx b/x-pack/plugins/reporting/public/share_context_menu/panel_spinner.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/panel_spinner.tsx rename to x-pack/plugins/reporting/public/share_context_menu/panel_spinner.tsx diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index 7fe5268fc9910..7165fcf6f8681 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -16,9 +16,9 @@ import type { ShareContext } from '../../../../../src/plugins/share/public'; import type { LicensingPluginSetup } from '../../../licensing/public'; import { CSV_JOB_TYPE } from '../../common/constants'; import type { JobParamsCSV } from '../../server/export_types/csv_searchsource/types'; -import { ReportingPanelContent } from '../components/reporting_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; import type { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ReportingPanelContent } from './reporting_panel_content_lazy'; export const ReportingCsvShareProvider = ({ apiClient, diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 42f6ee5fcb898..eb80f64be55e1 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -16,9 +16,9 @@ import type { LicensingPluginSetup } from '../../../licensing/public'; import type { LayoutParams } from '../../common/types'; import type { JobParamsPNG } from '../../server/export_types/png/types'; import type { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; -import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; import type { ReportingAPIClient } from '../lib/reporting_api_client'; +import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; interface JobParamsProviderOptions { shareableUrl: string; diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.test.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content.test.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/reporting_panel_content.test.tsx rename to x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content.test.tsx diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/reporting_panel_content.tsx rename to x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content.tsx diff --git a/x-pack/plugins/reporting/public/components/reporting_panel_content_lazy.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content_lazy.tsx similarity index 94% rename from x-pack/plugins/reporting/public/components/reporting_panel_content_lazy.tsx rename to x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content_lazy.tsx index 8a937f6d2eb36..a231d7aa881de 100644 --- a/x-pack/plugins/reporting/public/components/reporting_panel_content_lazy.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content_lazy.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { lazy, Suspense, FC } from 'react'; +import { FC, lazy, Suspense } from 'react'; import { PanelSpinner } from './panel_spinner'; import type { Props } from './reporting_panel_content'; diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.test.tsx b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.test.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/screen_capture_panel_content.test.tsx rename to x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.test.tsx diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx similarity index 100% rename from x-pack/plugins/reporting/public/components/screen_capture_panel_content.tsx rename to x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx diff --git a/x-pack/plugins/reporting/public/components/screen_capture_panel_content_lazy.tsx b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content_lazy.tsx similarity index 94% rename from x-pack/plugins/reporting/public/components/screen_capture_panel_content_lazy.tsx rename to x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content_lazy.tsx index 253b4fb88dcd4..a162dd749ff02 100644 --- a/x-pack/plugins/reporting/public/components/screen_capture_panel_content_lazy.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content_lazy.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { lazy, Suspense, FC } from 'react'; +import { FC, lazy, Suspense } from 'react'; import { PanelSpinner } from './panel_spinner'; import type { Props } from './screen_capture_panel_content'; diff --git a/x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx b/x-pack/plugins/reporting/public/shared/get_shared_components.tsx similarity index 79% rename from x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx rename to x-pack/plugins/reporting/public/shared/get_shared_components.tsx index 12d70c8701975..87ddf0cfdb389 100644 --- a/x-pack/plugins/reporting/public/components/shared/get_shared_components.tsx +++ b/x-pack/plugins/reporting/public/shared/get_shared_components.tsx @@ -7,10 +7,10 @@ import { CoreSetup } from 'kibana/public'; import React from 'react'; -import { ReportingAPIClient } from '../..'; -import { PDF_REPORT_TYPE } from '../../../common/constants'; -import type { Props as PanelPropsScreenCapture } from '../screen_capture_panel_content'; -import { ScreenCapturePanelContent } from '../screen_capture_panel_content_lazy'; +import { ReportingAPIClient } from '../'; +import { PDF_REPORT_TYPE } from '../../common/constants'; +import type { Props as PanelPropsScreenCapture } from '../share_context_menu/screen_capture_panel_content'; +import { ScreenCapturePanelContent } from '../share_context_menu/screen_capture_panel_content_lazy'; interface IncludeOnCloseFn { onClose: () => void; diff --git a/x-pack/plugins/reporting/public/components/shared/index.tsx b/x-pack/plugins/reporting/public/shared/index.ts similarity index 100% rename from x-pack/plugins/reporting/public/components/shared/index.tsx rename to x-pack/plugins/reporting/public/shared/index.ts From 633649460a59931eeb3f459da5e3eb334afdc83d Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 28 Jun 2021 17:25:24 -0700 Subject: [PATCH 39/74] [Enterprise Search] Improve flash messages screen reader UX (#103412) * Remove role region on flash messages - just `aria-live` is enough for screen readers to read it out, and `role` was causing "Flash messages" to get read out loud repeatedly between page navigation even when empty which was annoying and not good * Further a11y attribute recommendations from @myasonik --- .../shared/flash_messages/flash_messages.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx index ba42b89d6ab56..a96a179bd58c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/flash_messages/flash_messages.tsx @@ -10,7 +10,6 @@ import React, { Fragment } from 'react'; import { useValues, useActions } from 'kea'; import { EuiCallOut, EuiSpacer, EuiGlobalToastList } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FLASH_MESSAGE_TYPES, DEFAULT_TOAST_TIMEOUT } from './constants'; import { FlashMessagesLogic } from './flash_messages_logic'; @@ -19,14 +18,7 @@ export const FlashMessages: React.FC = ({ children }) => { const { messages } = useValues(FlashMessagesLogic); return ( -
    +
    {messages.map(({ type, message, description }, index) => ( Date: Mon, 28 Jun 2021 20:35:27 -0400 Subject: [PATCH 40/74] [Alerting] Enable rule import/export and allow rule types to exclude themselves from export (#102999) * Removing feature flag changes * Adding isExportable flag to rule type definition * Adding isExportable flag to rule type definition * Adding isExportable flag to rule type definition * Filtering rule on export by rule type isExportable flag * Fixing types * Adding docs * Fix condition when exportCount is 0 * Unit test for fix condition when exportCount is 0 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/api/alerting/legacy/list.asciidoc | 4 + docs/api/alerting/list_rule_types.asciidoc | 4 + .../alerting/create-and-manage-rules.asciidoc | 19 ++ .../server/saved_objects/routes/utils.test.ts | 16 ++ src/core/server/saved_objects/routes/utils.ts | 2 +- .../server/alert_types/always_firing.ts | 1 + .../server/alert_types/astros.ts | 1 + x-pack/plugins/alerting/README.md | 2 + x-pack/plugins/alerting/common/alert_type.ts | 1 + .../plugins/alerting/public/alert_api.test.ts | 3 + .../alert_navigation_registry.test.ts | 1 + .../server/alert_type_registry.test.ts | 16 ++ .../alerting/server/alert_type_registry.ts | 3 + .../alerts_client/tests/aggregate.test.ts | 2 + .../server/alerts_client/tests/create.test.ts | 1 + .../server/alerts_client/tests/find.test.ts | 2 + .../server/alerts_client/tests/lib.ts | 1 + .../tests/list_alert_types.test.ts | 5 + .../server/alerts_client/tests/update.test.ts | 3 + .../alerts_client_conflict_retries.test.ts | 2 + .../alerting_authorization.test.ts | 18 ++ .../alerting_authorization_kuery.test.ts | 10 + x-pack/plugins/alerting/server/config.test.ts | 1 - x-pack/plugins/alerting/server/config.ts | 1 - .../alerting/server/health/get_state.test.ts | 8 - .../alerting/server/lib/license_state.test.ts | 2 + x-pack/plugins/alerting/server/plugin.test.ts | 72 +----- x-pack/plugins/alerting/server/plugin.ts | 9 +- .../routes/legacy/list_alert_types.test.ts | 4 + .../alerting/server/routes/rule_types.test.ts | 5 + .../alerting/server/routes/rule_types.ts | 2 + .../alerting/server/saved_objects/index.ts | 111 +++++----- .../saved_objects/is_rule_exportable.test.ts | 208 ++++++++++++++++++ .../saved_objects/is_rule_exportable.ts | 33 +++ .../create_execution_handler.test.ts | 1 + .../server/task_runner/task_runner.test.ts | 1 + .../task_runner/task_runner_factory.test.ts | 1 + x-pack/plugins/alerting/server/types.ts | 1 + x-pack/plugins/apm/common/alert_types.ts | 5 + .../alerts/register_error_count_alert_type.ts | 1 + ...egister_transaction_duration_alert_type.ts | 1 + ...transaction_duration_anomaly_alert_type.ts | 1 + ...ister_transaction_error_rate_alert_type.ts | 1 + ...r_inventory_metric_threshold_alert_type.ts | 1 + .../register_log_threshold_alert_type.ts | 1 + .../register_metric_anomaly_alert_type.ts | 1 + .../register_metric_threshold_alert_type.ts | 1 + .../register_anomaly_detection_alert_type.ts | 1 + .../monitoring/server/alerts/base_alert.ts | 1 + .../utils/create_lifecycle_rule_type.test.ts | 1 + .../rules_notification_alert_type.ts | 1 + .../detection_engine/reference_rules/eql.ts | 1 + .../detection_engine/reference_rules/ml.ts | 1 + .../detection_engine/reference_rules/query.ts | 1 + .../reference_rules/threshold.ts | 1 + .../signals/signal_rule_alert_type.ts | 1 + .../server/alert_types/es_query/alert_type.ts | 1 + .../alert_types/geo_containment/alert_type.ts | 1 + .../alert_types/index_threshold/alert_type.ts | 1 + .../server/lib/alerts/duration_anomaly.ts | 1 + .../uptime/server/lib/alerts/status_check.ts | 1 + .../plugins/uptime/server/lib/alerts/tls.ts | 1 + .../uptime/server/lib/alerts/tls_legacy.ts | 1 + .../plugins/alerts/server/alert_types.ts | 13 ++ .../alerts_restricted/server/alert_types.ts | 2 + .../tests/alerting/rule_types.ts | 2 + .../spaces_only/tests/alerting/rule_types.ts | 2 + .../fixtures/plugins/alerts/server/plugin.ts | 3 + 68 files changed, 489 insertions(+), 139 deletions(-) create mode 100644 x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts create mode 100644 x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.ts diff --git a/docs/api/alerting/legacy/list.asciidoc b/docs/api/alerting/legacy/list.asciidoc index be37be36cd0e8..07307797c4223 100644 --- a/docs/api/alerting/legacy/list.asciidoc +++ b/docs/api/alerting/legacy/list.asciidoc @@ -80,6 +80,7 @@ The API returns the following: }, "producer":"stackAlerts", "minimumLicenseRequired":"basic", + "isExportable":true, "enabledInLicense":true, "authorizedConsumers":{ "alerts":{ @@ -113,6 +114,9 @@ Each alert type contains the following properties: | `minimumLicenseRequired` | The license required to use the alert type. +| `isExportable` +| Whether the rule type is exportable through the Saved Objects Management UI. + | `enabledInLicense` | Whether the alert type is enabled or disabled based on the license. diff --git a/docs/api/alerting/list_rule_types.asciidoc b/docs/api/alerting/list_rule_types.asciidoc index 31c8416e75059..21ace9f3105c0 100644 --- a/docs/api/alerting/list_rule_types.asciidoc +++ b/docs/api/alerting/list_rule_types.asciidoc @@ -82,6 +82,7 @@ The API returns the following: }, "producer":"stackAlerts", "minimum_license_required":"basic", + "is_exportable":true, "enabled_in_license":true, "authorized_consumers":{ "alerts":{ @@ -115,6 +116,9 @@ Each rule type contains the following properties: | `minimum_license_required` | The license required to use the rule type. +| `is_exportable` +| Whether the rule type is exportable through the Saved Objects Management UI. + | `enabled_in_license` | Whether the rule type is enabled or disabled based on the license. diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index af6714aef662f..cc91ebcd99be2 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -152,6 +152,25 @@ You can perform these operations in bulk by multi-selecting rules, and then clic [role="screenshot"] image:images/bulk-mute-disable.png[The Manage rules button lets you mute/unmute, enable/disable, and delete in bulk,width=75%] +[float] +[[importing-and-exporting-rules]] +=== Import and export rules + +To import and export rules, use the <>. + +[NOTE] +============================================== +Some rule types cannot be exported through this interface: + +**Security rules** can be imported and exported using the {security-guide}/rules-ui-management.html#import-export-rules-ui[Security UI]. + +**Stack monitoring rules** are <> for you and therefore cannot be managed via the Saved Objects Management UI. +============================================== + +Rules are disabled on export. You are prompted to re-enable rule on successful import. +[role="screenshot"] +image::images/rules-imported-banner.png[Rules import banner, width=50%] + [float] [[rule-details]] === Drilldown to rule details diff --git a/src/core/server/saved_objects/routes/utils.test.ts b/src/core/server/saved_objects/routes/utils.test.ts index 623d2dcc71fac..2127352e4c60e 100644 --- a/src/core/server/saved_objects/routes/utils.test.ts +++ b/src/core/server/saved_objects/routes/utils.test.ts @@ -101,6 +101,22 @@ describe('createSavedObjectsStreamFromNdJson', () => { }, ]); }); + + it('handles an ndjson stream that only contains excluded saved objects', async () => { + const savedObjectsStream = await createSavedObjectsStreamFromNdJson( + new Readable({ + read() { + this.push( + '{"excludedObjects":[{"id":"foo","reason":"excluded","type":"foo-type"}],"excludedObjectsCount":1,"exportedCount":0,"missingRefCount":0,"missingReferences":[]}\n' + ); + this.push(null); + }, + }) + ); + + const result = await readStreamToCompletion(savedObjectsStream); + expect(result).toEqual([]); + }); }); describe('validateTypes', () => { diff --git a/src/core/server/saved_objects/routes/utils.ts b/src/core/server/saved_objects/routes/utils.ts index e933badfe80fe..47996847a8387 100644 --- a/src/core/server/saved_objects/routes/utils.ts +++ b/src/core/server/saved_objects/routes/utils.ts @@ -32,7 +32,7 @@ export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) } }), createFilterStream( - (obj) => !!obj && !(obj as SavedObjectsExportResultDetails).exportedCount + (obj) => !!obj && (obj as SavedObjectsExportResultDetails).exportedCount === undefined ), createConcatStream([]), ]); diff --git a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts index 6e9ec0d367c9a..f056c292b018f 100644 --- a/x-pack/examples/alerting_example/server/alert_types/always_firing.ts +++ b/x-pack/examples/alerting_example/server/alert_types/always_firing.ts @@ -53,6 +53,7 @@ export const alertType: AlertType< ], defaultActionGroupId: DEFAULT_ACTION_GROUP, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ services, params: { instances = DEFAULT_INSTANCES_TO_GENERATE, thresholds }, diff --git a/x-pack/examples/alerting_example/server/alert_types/astros.ts b/x-pack/examples/alerting_example/server/alert_types/astros.ts index 45ea6b48bf6f4..8f9a293518300 100644 --- a/x-pack/examples/alerting_example/server/alert_types/astros.ts +++ b/x-pack/examples/alerting_example/server/alert_types/astros.ts @@ -51,6 +51,7 @@ export const alertType: AlertType< name: 'People In Space Right Now', actionGroups: [{ id: 'default', name: 'default' }], minimumLicenseRequired: 'basic', + isExportable: true, defaultActionGroupId: 'default', recoveryActionGroup: { id: 'hasLandedBackOnEarth', diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index 9d314cc048b70..62d2f2b57b8e8 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -118,6 +118,7 @@ The following table describes the properties of the `options` object. |executor|This is where the code for the rule type lives. This is a function to be called when executing a rule on an interval basis. For full details, see the executor section below.|Function| |producer|The id of the application producing this rule type.|string| |minimumLicenseRequired|The value of a minimum license. Most of the rules are licensed as "basic".|string| +|isExportable|Whether the rule type is exportable from the Saved Objects Management UI.|boolean| ### Executor @@ -262,6 +263,7 @@ const myRuleType: AlertType< ], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ alertId, startedAt, diff --git a/x-pack/plugins/alerting/common/alert_type.ts b/x-pack/plugins/alerting/common/alert_type.ts index e39c6d0a66f6c..e56034a4c41f8 100644 --- a/x-pack/plugins/alerting/common/alert_type.ts +++ b/x-pack/plugins/alerting/common/alert_type.ts @@ -20,6 +20,7 @@ export interface AlertType< defaultActionGroupId: ActionGroupIds; producer: string; minimumLicenseRequired: LicenseType; + isExportable: boolean; } export interface ActionGroup { diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerting/public/alert_api.test.ts index 023ea255e1c42..dd2f7d167c1c3 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerting/public/alert_api.test.ts @@ -24,6 +24,7 @@ describe('loadAlertTypes', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', }, @@ -49,6 +50,7 @@ describe('loadAlertType', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', }; @@ -71,6 +73,7 @@ describe('loadAlertType', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', }; diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts index 7eb5996311386..e7e311902d08d 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/alert_navigation_registry.test.ts @@ -20,6 +20,7 @@ const mockAlertType = (id: string): AlertType => ({ defaultActionGroupId: 'default', producer: 'alerts', minimumLicenseRequired: 'basic', + isExportable: true, }); describe('AlertNavigationRegistry', () => { diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index 7f34760c73199..63e381bc66c0a 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -47,6 +47,7 @@ describe('has()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }); @@ -67,6 +68,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -99,6 +101,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -129,6 +132,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -159,6 +163,7 @@ describe('register()', () => { executor: jest.fn(), producer: 'alerts', minimumLicenseRequired: 'basic', + isExportable: true, }; const registry = new AlertTypeRegistry(alertTypeRegistryParams); registry.register(alertType); @@ -203,6 +208,7 @@ describe('register()', () => { }, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -227,6 +233,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -257,6 +264,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }; @@ -279,6 +287,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }); @@ -294,6 +303,7 @@ describe('register()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }) @@ -315,6 +325,7 @@ describe('get()', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, executor: jest.fn(), producer: 'alerts', }); @@ -339,6 +350,7 @@ describe('get()', () => { "defaultActionGroupId": "default", "executor": [MockFunction], "id": "test", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "Test", "producer": "alerts", @@ -377,6 +389,7 @@ describe('list()', () => { }, ], defaultActionGroupId: 'testActionGroup', + isExportable: true, minimumLicenseRequired: 'basic', executor: jest.fn(), producer: 'alerts', @@ -403,6 +416,7 @@ describe('list()', () => { "defaultActionGroupId": "testActionGroup", "enabledInLicense": false, "id": "test", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "Test", "producer": "alerts", @@ -467,6 +481,7 @@ describe('ensureAlertTypeEnabled', () => { defaultActionGroupId: 'default', executor: jest.fn(), producer: 'alerts', + isExportable: true, minimumLicenseRequired: 'basic', recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, }); @@ -497,6 +512,7 @@ function alertTypeWithVariables( name: `${id}-name`, actionGroups: [], defaultActionGroupId: id, + isExportable: true, minimumLicenseRequired: 'basic', async executor() {}, producer: 'alerts', diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index 21feb76926791..64fca58c25e66 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -46,6 +46,7 @@ export interface RegistryAlertType | 'actionVariables' | 'producer' | 'minimumLicenseRequired' + | 'isExportable' > { id: string; enabledInLicense: boolean; @@ -250,6 +251,7 @@ export class AlertTypeRegistry { actionVariables, producer, minimumLicenseRequired, + isExportable, }, ]: [string, UntypedNormalizedAlertType]) => ({ id, @@ -260,6 +262,7 @@ export class AlertTypeRegistry { actionVariables, producer, minimumLicenseRequired, + isExportable, enabledInLicense: !!this.licenseState.getLicenseCheckForAlertType( id, name, diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts index bf966d38f6bc6..611ff23e46256 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts @@ -58,6 +58,7 @@ describe('aggregate()', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myType', name: 'myType', @@ -110,6 +111,7 @@ describe('aggregate()', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', authorizedConsumers: { diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts index 793357215d382..e231d1e3c27a2 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts @@ -1293,6 +1293,7 @@ describe('create()', () => { }), }, minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, producer: 'alerts', }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts index fe788cd43bc2b..5ec39681a758b 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts @@ -67,6 +67,7 @@ describe('find()', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, id: 'myType', name: 'myType', producer: 'myApp', @@ -126,6 +127,7 @@ describe('find()', () => { recoveryActionGroup: RecoveredActionGroup, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, producer: 'alerts', authorizedConsumers: { myApp: { read: true, all: true }, diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/lib.ts b/x-pack/plugins/alerting/server/alerts_client/tests/lib.ts index e4cd24ca7e49a..e0f4f9f6da0f1 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/lib.ts @@ -88,6 +88,7 @@ export function getBeforeSetup( recoveryActionGroup: RecoveredActionGroup, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, producer: 'alerts', })); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts index 9fe33996b9edf..0f849423409d8 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts @@ -58,6 +58,7 @@ describe('listAlertTypes', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'alertingAlertType', name: 'alertingAlertType', @@ -69,6 +70,7 @@ describe('listAlertTypes', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -110,6 +112,7 @@ describe('listAlertTypes', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myType', name: 'myType', @@ -122,6 +125,7 @@ describe('listAlertTypes', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', enabledInLicense: true, @@ -139,6 +143,7 @@ describe('listAlertTypes', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, producer: 'alerts', authorizedConsumers: { diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts index 350c9ed31298f..2de56d20702f4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts @@ -127,6 +127,7 @@ describe('update()', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, async executor() {}, producer: 'alerts', @@ -773,6 +774,7 @@ describe('update()', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, validate: { params: schema.object({ @@ -1096,6 +1098,7 @@ describe('update()', () => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, async executor() {}, producer: 'alerts', diff --git a/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts index 98ad427d0c37b..e45b3513eef26 100644 --- a/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts @@ -335,6 +335,7 @@ beforeEach(() => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, async executor() {}, producer: 'alerts', @@ -346,6 +347,7 @@ beforeEach(() => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, async executor() {}, producer: 'alerts', diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index 2227e0cecd0a6..c07148f03c684 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -203,6 +203,7 @@ beforeEach(() => { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, async executor() {}, producer: 'myApp', @@ -892,6 +893,7 @@ describe('AlertingAuthorization', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myOtherAppAlertType', name: 'myOtherAppAlertType', @@ -903,6 +905,7 @@ describe('AlertingAuthorization', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -914,6 +917,7 @@ describe('AlertingAuthorization', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'mySecondAppAlertType', name: 'mySecondAppAlertType', @@ -1242,6 +1246,7 @@ describe('AlertingAuthorization', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myOtherAppAlertType', name: 'myOtherAppAlertType', @@ -1253,6 +1258,7 @@ describe('AlertingAuthorization', () => { actionVariables: undefined, defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -1300,6 +1306,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1328,6 +1335,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", @@ -1387,6 +1395,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1423,6 +1432,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", @@ -1502,6 +1512,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", @@ -1526,6 +1537,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1605,6 +1617,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", @@ -1633,6 +1646,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1703,6 +1717,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1807,6 +1822,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", @@ -1831,6 +1847,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myAppAlertType", "producer": "myApp", @@ -1914,6 +1931,7 @@ describe('AlertingAuthorization', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "myOtherAppAlertType", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "myOtherAppAlertType", "producer": "myOtherApp", diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts index 8a558b6427383..7d39380f7bd1a 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts @@ -26,6 +26,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { name: 'myAppAlertType', producer: 'myApp', minimumLicenseRequired: 'basic', + isExportable: true, authorizedConsumers: { myApp: { read: true, all: true }, }, @@ -53,6 +54,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -88,6 +90,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -104,6 +107,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myOtherAppAlertType', name: 'myOtherAppAlertType', @@ -120,6 +124,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'mySecondAppAlertType', name: 'mySecondAppAlertType', @@ -162,6 +167,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { name: 'myAppAlertType', producer: 'myApp', minimumLicenseRequired: 'basic', + isExportable: true, authorizedConsumers: { myApp: { read: true, all: true }, }, @@ -216,6 +222,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -283,6 +290,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myAppAlertType', name: 'myAppAlertType', @@ -299,6 +307,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'myOtherAppAlertType', name: 'myOtherAppAlertType', @@ -315,6 +324,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { actionGroups: [], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, id: 'mySecondAppAlertType', name: 'mySecondAppAlertType', diff --git a/x-pack/plugins/alerting/server/config.test.ts b/x-pack/plugins/alerting/server/config.test.ts index a8befe5210752..f7280e05b78f3 100644 --- a/x-pack/plugins/alerting/server/config.test.ts +++ b/x-pack/plugins/alerting/server/config.test.ts @@ -12,7 +12,6 @@ describe('config validation', () => { const config: Record = {}; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { - "enableImportExport": false, "healthCheck": Object { "interval": "60m", }, diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index d50917fd13578..e42955b385bf1 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -16,7 +16,6 @@ export const configSchema = schema.object({ interval: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '1h' }), }), - enableImportExport: schema.boolean({ defaultValue: false }), }); export type AlertsConfig = TypeOf; diff --git a/x-pack/plugins/alerting/server/health/get_state.test.ts b/x-pack/plugins/alerting/server/health/get_state.test.ts index 2dddf81e3b766..24f3c101b26b6 100644 --- a/x-pack/plugins/alerting/server/health/get_state.test.ts +++ b/x-pack/plugins/alerting/server/health/get_state.test.ts @@ -71,7 +71,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }), pollInterval ).subscribe(); @@ -105,7 +104,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }), pollInterval, retryDelay @@ -150,7 +148,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }) ).toPromise(); @@ -181,7 +178,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }) ).toPromise(); @@ -212,7 +208,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }) ).toPromise(); @@ -240,7 +235,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }), retryDelay ).subscribe((status) => { @@ -271,7 +265,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }), retryDelay ).subscribe((status) => { @@ -308,7 +301,6 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }) ).toPromise(); diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index a1c326656f735..e04ce85b35374 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -70,6 +70,7 @@ describe('getLicenseCheckForAlertType', () => { executor: jest.fn(), producer: 'alerts', minimumLicenseRequired: 'gold', + isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, }; @@ -204,6 +205,7 @@ describe('ensureLicenseForAlertType()', () => { executor: jest.fn(), producer: 'alerts', minimumLicenseRequired: 'gold', + isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, }; diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 4e9249944a6bf..9adc3cc9d6569 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -18,7 +18,6 @@ import { AlertsConfig } from './config'; import { AlertType } from './types'; import { eventLogMock } from '../../event_log/server/mocks'; import { actionsMock } from '../../actions/server/mocks'; -import mappings from './saved_objects/mappings.json'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -37,7 +36,6 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }); plugin = new AlertingPlugin(context); @@ -61,78 +59,13 @@ describe('Alerting Plugin', () => { ); }); - it('should register saved object with no management capability if enableImportExport is false', async () => { - const context = coreMock.createPluginInitializerContext({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - enableImportExport: false, - }); - plugin = new AlertingPlugin(context); - - const setupMocks = coreMock.createSetup(); - await plugin.setup(setupMocks, { - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - actions: actionsMock.createSetup(), - statusService: statusServiceMock.createSetupContract(), - }); - - expect(setupMocks.savedObjects.registerType).toHaveBeenCalledTimes(2); - const registerAlertingSavedObject = setupMocks.savedObjects.registerType.mock.calls[0][0]; - expect(registerAlertingSavedObject.name).toEqual('alert'); - expect(registerAlertingSavedObject.hidden).toBe(true); - expect(registerAlertingSavedObject.mappings).toEqual(mappings.alert); - expect(registerAlertingSavedObject.management).toBeUndefined(); - }); - - it('should register saved object with import/export capability if enableImportExport is true', async () => { - const context = coreMock.createPluginInitializerContext({ - healthCheck: { - interval: '5m', - }, - invalidateApiKeysTask: { - interval: '5m', - removalDelay: '1h', - }, - enableImportExport: true, - }); - plugin = new AlertingPlugin(context); - - const setupMocks = coreMock.createSetup(); - await plugin.setup(setupMocks, { - licensing: licensingMock.createSetup(), - encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), - taskManager: taskManagerMock.createSetup(), - eventLog: eventLogServiceMock.create(), - actions: actionsMock.createSetup(), - statusService: statusServiceMock.createSetupContract(), - }); - - expect(setupMocks.savedObjects.registerType).toHaveBeenCalledTimes(2); - const registerAlertingSavedObject = setupMocks.savedObjects.registerType.mock.calls[0][0]; - expect(registerAlertingSavedObject.name).toEqual('alert'); - expect(registerAlertingSavedObject.hidden).toBe(true); - expect(registerAlertingSavedObject.mappings).toEqual(mappings.alert); - expect(registerAlertingSavedObject.management).not.toBeUndefined(); - expect(registerAlertingSavedObject.management?.importableAndExportable).toBe(true); - expect(registerAlertingSavedObject.management?.getTitle).not.toBeUndefined(); - expect(registerAlertingSavedObject.management?.onImport).not.toBeUndefined(); - expect(registerAlertingSavedObject.management?.onExport).not.toBeUndefined(); - }); - describe('registerType()', () => { let setup: PluginSetupContract; const sampleAlertType: AlertType = { id: 'test', name: 'test', minimumLicenseRequired: 'basic', + isExportable: true, actionGroups: [], defaultActionGroupId: 'default', producer: 'test', @@ -189,7 +122,6 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }); const plugin = new AlertingPlugin(context); @@ -229,7 +161,6 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }); const plugin = new AlertingPlugin(context); @@ -283,7 +214,6 @@ describe('Alerting Plugin', () => { interval: '5m', removalDelay: '1h', }, - enableImportExport: false, }); const plugin = new AlertingPlugin(context); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 5afa1b235a8c1..df63625bf242d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -192,8 +192,6 @@ export class AlertingPlugin { event: { provider: EVENT_LOG_PROVIDER }, }); - setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects, this.config); - this.eventLogService = plugins.eventLog; plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); @@ -221,6 +219,13 @@ export class AlertingPlugin { }); } + setupSavedObjects( + core.savedObjects, + plugins.encryptedSavedObjects, + this.alertTypeRegistry, + this.logger + ); + initializeApiKeyInvalidator( this.logger, core.getStartServices(), diff --git a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts index 3e6f2f484a6d8..e2bf1afdb0f6e 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts @@ -47,6 +47,7 @@ describe('listAlertTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { @@ -79,6 +80,7 @@ describe('listAlertTypesRoute', () => { "defaultActionGroupId": "default", "enabledInLicense": true, "id": "1", + "isExportable": true, "minimumLicenseRequired": "basic", "name": "name", "producer": "test", @@ -120,6 +122,7 @@ describe('listAlertTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { @@ -172,6 +175,7 @@ describe('listAlertTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { diff --git a/x-pack/plugins/alerting/server/routes/rule_types.test.ts b/x-pack/plugins/alerting/server/routes/rule_types.test.ts index 58c9a4b4c46fd..4f04f8c7575c5 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.test.ts @@ -48,6 +48,7 @@ describe('ruleTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { @@ -70,6 +71,7 @@ describe('ruleTypesRoute', () => { ], default_action_group_id: 'default', minimum_license_required: 'basic', + is_exportable: true, recovery_action_group: RecoveredActionGroup, authorized_consumers: {}, action_variables: { @@ -102,6 +104,7 @@ describe('ruleTypesRoute', () => { "default_action_group_id": "default", "enabled_in_license": true, "id": "1", + "is_exportable": true, "minimum_license_required": "basic", "name": "name", "producer": "test", @@ -143,6 +146,7 @@ describe('ruleTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { @@ -195,6 +199,7 @@ describe('ruleTypesRoute', () => { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, authorizedConsumers: {}, actionVariables: { diff --git a/x-pack/plugins/alerting/server/routes/rule_types.ts b/x-pack/plugins/alerting/server/routes/rule_types.ts index a3a44f9b013cd..f67e07f13feed 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.ts @@ -19,6 +19,7 @@ const rewriteBodyRes: RewriteResponseCase = (result actionGroups, defaultActionGroupId, minimumLicenseRequired, + isExportable, actionVariables, authorizedConsumers, ...rest @@ -29,6 +30,7 @@ const rewriteBodyRes: RewriteResponseCase = (result action_groups: actionGroups, default_action_group_id: defaultActionGroupId, minimum_license_required: minimumLicenseRequired, + is_exportable: isExportable, action_variables: actionVariables, authorized_consumers: authorizedConsumers, }) diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 1ad0f972b2ec0..88ee3179ab3d8 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -6,6 +6,7 @@ */ import type { + Logger, SavedObject, SavedObjectsExportTransformContext, SavedObjectsServiceSetup, @@ -17,7 +18,9 @@ import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objec import { transformRulesForExport } from './transform_rule_for_export'; import { RawAlert } from '../types'; import { getImportWarnings } from './get_import_warnings'; -import { AlertsConfig } from '../config'; +import { isRuleExportable } from './is_rule_exportable'; +import { AlertTypeRegistry } from '../alert_type_registry'; + export { partiallyUpdateAlert } from './partially_update_alert'; export const AlertAttributesExcludedFromAAD = [ @@ -44,65 +47,63 @@ export type AlertAttributesExcludedFromAADType = export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, - alertingConfig: Promise + ruleTypeRegistry: AlertTypeRegistry, + logger: Logger ) { - alertingConfig.then((config: AlertsConfig) => { - savedObjects.registerType({ - name: 'alert', - hidden: true, - namespaceType: 'single', - migrations: getMigrations(encryptedSavedObjects), - mappings: mappings.alert as SavedObjectsTypeMappingDefinition, - ...(config.enableImportExport - ? { - management: { - importableAndExportable: true, - getTitle(ruleSavedObject: SavedObject) { - return `Rule: [${ruleSavedObject.attributes.name}]`; - }, - onImport(ruleSavedObjects) { - return { - warnings: getImportWarnings(ruleSavedObjects), - }; - }, - onExport( - context: SavedObjectsExportTransformContext, - objects: Array> - ) { - return transformRulesForExport(objects); - }, - }, - } - : {}), - }); + savedObjects.registerType({ + name: 'alert', + hidden: true, + namespaceType: 'single', + migrations: getMigrations(encryptedSavedObjects), + mappings: mappings.alert as SavedObjectsTypeMappingDefinition, + management: { + importableAndExportable: true, + getTitle(ruleSavedObject: SavedObject) { + return `Rule: [${ruleSavedObject.attributes.name}]`; + }, + onImport(ruleSavedObjects) { + return { + warnings: getImportWarnings(ruleSavedObjects), + }; + }, + onExport( + context: SavedObjectsExportTransformContext, + objects: Array> + ) { + return transformRulesForExport(objects); + }, + isExportable(ruleSavedObject: SavedObject) { + return isRuleExportable(ruleSavedObject, ruleTypeRegistry, logger); + }, + }, + }); - savedObjects.registerType({ - name: 'api_key_pending_invalidation', - hidden: true, - namespaceType: 'agnostic', - mappings: { - properties: { - apiKeyId: { - type: 'keyword', - }, - createdAt: { - type: 'date', - }, + savedObjects.registerType({ + name: 'api_key_pending_invalidation', + hidden: true, + namespaceType: 'agnostic', + mappings: { + properties: { + apiKeyId: { + type: 'keyword', + }, + createdAt: { + type: 'date', }, }, - }); + }, + }); - // Encrypted attributes - encryptedSavedObjects.registerType({ - type: 'alert', - attributesToEncrypt: new Set(['apiKey']), - attributesToExcludeFromAAD: new Set(AlertAttributesExcludedFromAAD), - }); + // Encrypted attributes + encryptedSavedObjects.registerType({ + type: 'alert', + attributesToEncrypt: new Set(['apiKey']), + attributesToExcludeFromAAD: new Set(AlertAttributesExcludedFromAAD), + }); - // Encrypted attributes - encryptedSavedObjects.registerType({ - type: 'api_key_pending_invalidation', - attributesToEncrypt: new Set(['apiKeyId']), - }); + // Encrypted attributes + encryptedSavedObjects.registerType({ + type: 'api_key_pending_invalidation', + attributesToEncrypt: new Set(['apiKeyId']), }); } diff --git a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts new file mode 100644 index 0000000000000..cc2dfbd3e2d2f --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockedLogger, loggerMock } from '@kbn/logging/target/mocks'; +import { TaskRunnerFactory } from '../task_runner'; +import { AlertTypeRegistry, ConstructorOptions } from '../alert_type_registry'; +import { taskManagerMock } from '../../../task_manager/server/mocks'; +import { ILicenseState } from '../lib/license_state'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { licensingMock } from '../../../licensing/server/mocks'; +import { isRuleExportable } from './is_rule_exportable'; + +let ruleTypeRegistryParams: ConstructorOptions; +let logger: MockedLogger; +let mockedLicenseState: jest.Mocked; +const taskManager = taskManagerMock.createSetup(); + +beforeEach(() => { + jest.resetAllMocks(); + mockedLicenseState = licenseStateMock.create(); + logger = loggerMock.create(); + ruleTypeRegistryParams = { + taskManager, + taskRunnerFactory: new TaskRunnerFactory(), + licenseState: mockedLicenseState, + licensing: licensingMock.createSetup(), + }; +}); + +describe('isRuleExportable', () => { + it('should return true if rule type isExportable is true', () => { + const registry = new AlertTypeRegistry(ruleTypeRegistryParams); + registry.register({ + id: 'foo', + name: 'Foo', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + producer: 'alerts', + }); + expect( + isRuleExportable( + { + id: '1', + type: 'alert', + attributes: { + enabled: true, + name: 'rule-name', + tags: ['tag-1', 'tag-2'], + alertTypeId: 'foo', + consumer: 'alert-consumer', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: 'me', + updatedBy: 'me', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + apiKey: '4tndskbuhewotw4klrhgjewrt9u', + apiKeyOwner: 'me', + throttle: null, + notifyWhen: 'onActionGroupChange', + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'active', + lastExecutionDate: '2020-08-20T19:23:38Z', + error: null, + }, + scheduledTaskId: '2q5tjbf3q45twer', + }, + references: [], + }, + registry, + logger + ) + ).toEqual(true); + }); + + it('should return false and log warning if rule type isExportable is false', () => { + const registry = new AlertTypeRegistry(ruleTypeRegistryParams); + registry.register({ + id: 'foo', + name: 'Foo', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: false, + executor: jest.fn(), + producer: 'alerts', + }); + expect( + isRuleExportable( + { + id: '1', + type: 'alert', + attributes: { + enabled: true, + name: 'rule-name', + tags: ['tag-1', 'tag-2'], + alertTypeId: 'foo', + consumer: 'alert-consumer', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: 'me', + updatedBy: 'me', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + apiKey: '4tndskbuhewotw4klrhgjewrt9u', + apiKeyOwner: 'me', + throttle: null, + notifyWhen: 'onActionGroupChange', + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'active', + lastExecutionDate: '2020-08-20T19:23:38Z', + error: null, + }, + scheduledTaskId: '2q5tjbf3q45twer', + }, + references: [], + }, + registry, + logger + ) + ).toEqual(false); + expect(logger.warn).toHaveBeenCalledWith( + `Skipping export of rule \"1\" because rule type \"foo\" is not exportable through this interface.` + ); + }); + + it('should return false and log warning if rule type is not registered', () => { + const registry = new AlertTypeRegistry(ruleTypeRegistryParams); + registry.register({ + id: 'foo', + name: 'Foo', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: false, + executor: jest.fn(), + producer: 'alerts', + }); + expect( + isRuleExportable( + { + id: '1', + type: 'alert', + attributes: { + enabled: true, + name: 'rule-name', + tags: ['tag-1', 'tag-2'], + alertTypeId: 'bar', + consumer: 'alert-consumer', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: 'me', + updatedBy: 'me', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + apiKey: '4tndskbuhewotw4klrhgjewrt9u', + apiKeyOwner: 'me', + throttle: null, + notifyWhen: 'onActionGroupChange', + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'active', + lastExecutionDate: '2020-08-20T19:23:38Z', + error: null, + }, + scheduledTaskId: '2q5tjbf3q45twer', + }, + references: [], + }, + registry, + logger + ) + ).toEqual(false); + expect(logger.warn).toHaveBeenCalledWith( + `Skipping export of rule \"1\" because rule type \"bar\" is not recognized.` + ); + }); +}); diff --git a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.ts b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.ts new file mode 100644 index 0000000000000..38290e5f465cc --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Logger, SavedObject } from 'kibana/server'; +import { RawAlert } from '../types'; +import { AlertTypeRegistry } from '../alert_type_registry'; + +export function isRuleExportable( + rule: SavedObject, + ruleTypeRegistry: AlertTypeRegistry, + logger: Logger +): boolean { + const ruleSO = rule as SavedObject; + try { + const ruleType = ruleTypeRegistry.get(ruleSO.attributes.alertTypeId); + if (!ruleType.isExportable) { + logger.warn( + `Skipping export of rule "${ruleSO.id}" because rule type "${ruleSO.attributes.alertTypeId}" is not exportable through this interface.` + ); + } + + return ruleType.isExportable; + } catch (err) { + logger.warn( + `Skipping export of rule "${ruleSO.id}" because rule type "${ruleSO.attributes.alertTypeId}" is not recognized.` + ); + return false; + } +} diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 1dcd19119b6fd..b264428b4d6f2 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -44,6 +44,7 @@ const alertType: NormalizedAlertType< ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 88d1b1b24a4ec..4f650975f830e 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -44,6 +44,7 @@ const alertType: jest.Mocked = { actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: RecoveredActionGroup, executor: jest.fn(), producer: 'alerts', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 343dffa0d5e70..050345f3e617f 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -26,6 +26,7 @@ const alertType: UntypedNormalizedAlertType = { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: { id: 'recovered', name: 'Recovered', diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index f8846035e6b02..f21e17adc841d 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -146,6 +146,7 @@ export interface AlertType< params?: ActionVariable[]; }; minimumLicenseRequired: LicenseType; + isExportable: boolean; } export type UntypedAlertType = AlertType< diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts index 12df93d54b296..ad233c7f6df92 100644 --- a/x-pack/plugins/apm/common/alert_types.ts +++ b/x-pack/plugins/apm/common/alert_types.ts @@ -33,6 +33,7 @@ export const ALERT_TYPES_CONFIG: Record< actionGroups: Array>; defaultActionGroupId: ThresholdMetActionGroupId; minimumLicenseRequired: string; + isExportable: boolean; producer: string; } > = { @@ -44,6 +45,7 @@ export const ALERT_TYPES_CONFIG: Record< defaultActionGroupId: THRESHOLD_MET_GROUP_ID, minimumLicenseRequired: 'basic', producer: 'apm', + isExportable: true, }, [AlertType.TransactionDuration]: { name: i18n.translate('xpack.apm.transactionDurationAlert.name', { @@ -53,6 +55,7 @@ export const ALERT_TYPES_CONFIG: Record< defaultActionGroupId: THRESHOLD_MET_GROUP_ID, minimumLicenseRequired: 'basic', producer: 'apm', + isExportable: true, }, [AlertType.TransactionDurationAnomaly]: { name: i18n.translate('xpack.apm.transactionDurationAnomalyAlert.name', { @@ -62,6 +65,7 @@ export const ALERT_TYPES_CONFIG: Record< defaultActionGroupId: THRESHOLD_MET_GROUP_ID, minimumLicenseRequired: 'basic', producer: 'apm', + isExportable: true, }, [AlertType.TransactionErrorRate]: { name: i18n.translate('xpack.apm.transactionErrorRateAlert.name', { @@ -71,6 +75,7 @@ export const ALERT_TYPES_CONFIG: Record< defaultActionGroupId: THRESHOLD_MET_GROUP_ID, minimumLicenseRequired: 'basic', producer: 'apm', + isExportable: true, }, }; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts index 2a63c53b626cd..7548d6eba060a 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_count_alert_type.ts @@ -71,6 +71,7 @@ export function registerErrorCountAlertType({ }, producer: 'apm', minimumLicenseRequired: 'basic', + isExportable: true, executor: async ({ services, params }) => { const config = await config$.pipe(take(1)).toPromise(); const alertParams = params; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 24a6376761a7d..ca7806251f75e 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -79,6 +79,7 @@ export function registerTransactionDurationAlertType({ }, producer: 'apm', minimumLicenseRequired: 'basic', + isExportable: true, executor: async ({ services, params }) => { const config = await config$.pipe(take(1)).toPromise(); const alertParams = params; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts index f640925b0a0fa..de0657d075d7f 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_anomaly_alert_type.ts @@ -87,6 +87,7 @@ export function registerTransactionDurationAnomalyAlertType({ }, producer: 'apm', minimumLicenseRequired: 'basic', + isExportable: true, executor: async ({ services, params }) => { if (!ml) { return {}; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts index 47ed5236ce74c..718ffd9c92167 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_error_rate_alert_type.ts @@ -77,6 +77,7 @@ export function registerTransactionErrorRateAlertType({ }, producer: 'apm', minimumLicenseRequired: 'basic', + isExportable: true, executor: async ({ services, params: alertParams }) => { const config = await config$.pipe(take(1)).toPromise(); const indices = await getApmIndices({ diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts index 81204c7b71a68..a2d8e522c7c8d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts @@ -89,6 +89,7 @@ export const registerMetricInventoryThresholdAlertType = ( actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], producer: 'infrastructure', minimumLicenseRequired: 'basic', + isExportable: true, executor: createInventoryMetricThresholdExecutor(libs), actionVariables: { context: [ diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts index b7c6881b20390..62d92d0487ff7 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -107,6 +107,7 @@ export async function registerLogThresholdAlertType( defaultActionGroupId: FIRED_ACTIONS.id, actionGroups: [FIRED_ACTIONS], minimumLicenseRequired: 'basic', + isExportable: true, executor: createLogThresholdExecutor(libs), actionVariables: { context: [ diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts index 2adf37c60cf3a..63354111a1a98 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/register_metric_anomaly_alert_type.ts @@ -64,6 +64,7 @@ export const registerMetricAnomalyAlertType = ( actionGroups: [FIRED_ACTIONS], producer: 'infrastructure', minimumLicenseRequired: 'basic', + isExportable: true, executor: createMetricAnomalyExecutor(libs, ml), actionVariables: { context: [ diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index 7dbae03f20fbd..e519d67b446a5 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -100,6 +100,7 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs): Metric defaultActionGroupId: FIRED_ACTIONS.id, actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS], minimumLicenseRequired: 'basic', + isExportable: true, executor: createMetricThresholdExecutor(libs), actionVariables: { context: [ diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index f39b3850b71b1..93c627c0f6311 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -119,6 +119,7 @@ export function registerAnomalyDetectionAlertType({ }, producer: PLUGIN_ID, minimumLicenseRequired: MINIMUM_FULL_LICENSE, + isExportable: true, async executor({ services, params, alertId, state, previousStartedAt, startedAt }) { const fakeRequest = {} as KibanaRequest; const { execute } = mlSharedServices.alertingServiceProvider( diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index bb80d84210a48..8954a4ae2486d 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -96,6 +96,7 @@ export class BaseAlert { ], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: false, executor: ( options: AlertExecutorOptions & { state: ExecutedState; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 85e69eb51fd02..a362dcccc2f0f 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -40,6 +40,7 @@ function createRule() { }, id: 'test_type', minimumLicenseRequired: 'basic', + isExportable: true, name: 'Test type', producer: 'test', actionVariables: { 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 a4863e577c6bc..c85848ba6dcfe 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 @@ -37,6 +37,7 @@ export const rulesNotificationAlertType = ({ }), }, minimumLicenseRequired: 'basic', + isExportable: false, async executor({ startedAt, previousStartedAt, alertId, services, params }) { const ruleAlertSavedObject = await services.savedObjectsClient.get( 'alert', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts index 39d02c808d09e..b98bd9b3551c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts @@ -45,6 +45,7 @@ export const createEqlAlertType = (ruleDataClient: RuleDataClient, logger: Logge context: [{ name: 'server', description: 'the server' }], }, minimumLicenseRequired: 'basic', + isExportable: false, producer: 'security-solution', async executor({ startedAt, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts index c07d0436cc90d..14252bf62ef83 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts @@ -57,6 +57,7 @@ export const mlAlertType = createSecurityMlRuleType({ context: [{ name: 'server', description: 'the server' }], }, minimumLicenseRequired: 'basic', + isExportable: false, producer: 'security-solution', async executor({ services: { alertWithPersistence, findAlerts }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts index 39f325fd6cf8f..4ca9448f5e3c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts @@ -43,6 +43,7 @@ export const createQueryAlertType = (ruleDataClient: RuleDataClient, logger: Log context: [{ name: 'server', description: 'the server' }], }, minimumLicenseRequired: 'basic', + isExportable: false, producer: 'security-solution', async executor({ services: { alertWithPersistence, findAlerts }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts index d4721e8bab11d..fa291ef3139cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts @@ -113,6 +113,7 @@ export const createThresholdAlertType = (ruleDataClient: RuleDataClient, logger: context: [{ name: 'server', description: 'the server' }], }, minimumLicenseRequired: 'basic', + isExportable: false, producer: 'security-solution', async executor({ startedAt, services, params, alertId }) { const fromDate = moment(startedAt).subtract(moment.duration(5, 'm')); // hardcoded 5-minute rule interval 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 32bd6d71bfb1d..ba665fa43e8b8 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 @@ -103,6 +103,7 @@ export const signalRulesAlertType = ({ }, producer: SERVER_APP_ID, minimumLicenseRequired: 'basic', + isExportable: false, async executor({ previousStartedAt, startedAt, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index b81bc19d5c731..c9f233002d79b 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -140,6 +140,7 @@ export function getAlertType( ], }, minimumLicenseRequired: 'basic', + isExportable: true, executor, producer: STACK_ALERTS_FEATURE_ID, }; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index e3d379f2869b9..43ae726fa2478 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -177,5 +177,6 @@ export function getAlertType(logger: Logger): GeoContainmentAlertType { }, actionVariables, minimumLicenseRequired: 'gold', + isExportable: true, }; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index a242c1e0eb29e..aa56951b5dcba 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -125,6 +125,7 @@ export function getAlertType( ], }, minimumLicenseRequired: 'basic', + isExportable: true, executor, producer: STACK_ALERTS_FEATURE_ID, }; diff --git a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts index b3262976b0cac..981a7e7ca3920 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/duration_anomaly.ts @@ -89,6 +89,7 @@ export const durationAnomalyAlertFactory: UptimeAlertTypeFactory state: [...durationAnomalyTranslations.actionVariables, ...commonStateTranslations], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ options, uptimeEsClient, savedObjectsClient, dynamicSettings }) { const { services: { alertInstanceFactory }, diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index c5a6ef877c47a..6f3e3303f6bdc 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -258,6 +258,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = ( state: [...commonMonitorStateI18, ...commonStateTranslations], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ options: { params: rawParams, diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index f29744fdbb70f..09f5e2fe0f6d5 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -112,6 +112,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, state: [...tlsTranslations.actionVariables, ...commonStateTranslations], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ options, dynamicSettings, uptimeEsClient }) { const { services: { alertInstanceFactory }, diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts index 8f1c0093e60ac..5bf91b7c5486d 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls_legacy.ts @@ -103,6 +103,7 @@ export const tlsLegacyAlertFactory: UptimeAlertTypeFactory = (_s state: [...tlsTranslations.actionVariables, ...commonStateTranslations], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor({ options, dynamicSettings, uptimeEsClient }) { const { services: { alertInstanceFactory }, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts index 3e622b49d03df..3c9d783f5a357 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/alert_types.ts @@ -81,6 +81,7 @@ function getAlwaysFiringAlertType() { producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, actionVariables: { state: [{ name: 'instanceStateValue', description: 'the instance state value' }], params: [{ name: 'instanceParamsValue', description: 'the instance params value' }], @@ -167,6 +168,7 @@ function getCumulativeFiringAlertType() { producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor(alertExecutorOptions) { const { services, state } = alertExecutorOptions; const group = 'default'; @@ -212,6 +214,7 @@ function getNeverFiringAlertType() { producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor({ services, params, state }) { await services.scopedClusterClient.asCurrentUser.index({ index: params.index, @@ -252,6 +255,7 @@ function getFailingAlertType() { producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor({ services, params, state }) { await services.scopedClusterClient.asCurrentUser.index({ index: params.index, @@ -290,6 +294,7 @@ function getAuthorizationAlertType(core: CoreSetup) { defaultActionGroupId: 'default', producer: 'alertsFixture', minimumLicenseRequired: 'basic', + isExportable: true, validate: { params: paramsSchema, }, @@ -376,6 +381,7 @@ function getValidationAlertType() { ], producer: 'alertsFixture', minimumLicenseRequired: 'basic', + isExportable: true, defaultActionGroupId: 'default', validate: { params: paramsSchema, @@ -404,6 +410,7 @@ function getPatternFiringAlertType() { producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor(alertExecutorOptions) { const { services, state, params } = alertExecutorOptions; const pattern = params.pattern; @@ -468,6 +475,7 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, }; const goldNoopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { @@ -477,6 +485,7 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'gold', + isExportable: true, async executor() {}, }; const onlyContextVariablesAlertType: AlertType<{}, {}, {}, {}, 'default'> = { @@ -486,6 +495,7 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, actionVariables: { context: [{ name: 'aContextVariable', description: 'this is a context variable' }], }, @@ -501,6 +511,7 @@ export function defineAlertTypes( state: [{ name: 'aStateVariable', description: 'this is a state variable' }], }, minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, }; const throwAlertType: AlertType<{}, {}, {}, {}, 'default'> = { @@ -515,6 +526,7 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() { throw new Error('this alert is intended to fail'); }, @@ -531,6 +543,7 @@ export function defineAlertTypes( producer: 'alertsFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() { await new Promise((resolve) => setTimeout(resolve, 5000)); }, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts index 884af7801855f..2255d1fa95e2d 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts_restricted/server/alert_types.ts @@ -20,6 +20,7 @@ export function defineAlertTypes( producer: 'alertsRestrictedFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, recoveryActionGroup: { id: 'restrictedRecovered', name: 'Restricted Recovery' }, async executor() {}, }; @@ -30,6 +31,7 @@ export function defineAlertTypes( producer: 'alertsRestrictedFixture', defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, }; alerting.registerType(noopRestrictedAlertType); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts index c851aaf2bbc88..f52f0977a630b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rule_types.ts @@ -30,6 +30,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }, producer: 'alertsFixture', minimum_license_required: 'basic', + is_exportable: true, recovery_action_group: { id: 'recovered', name: 'Recovered', @@ -56,6 +57,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }, producer: 'alertsRestrictedFixture', minimum_license_required: 'basic', + is_exportable: true, enabled_in_license: true, }; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts index 3d3cec4c30252..86a0e269b26d6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts @@ -42,6 +42,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }, producer: 'alertsFixture', minimum_license_required: 'basic', + is_exportable: true, enabled_in_license: true, }); expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture'); @@ -126,6 +127,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { }, producer: 'alertsFixture', minimumLicenseRequired: 'basic', + isExportable: true, enabledInLicense: true, }); expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture'); diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts index 10a81e4309088..8d0d2c4f0be33 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/server/plugin.ts @@ -24,6 +24,7 @@ export const noopAlertType: AlertType<{}, {}, {}, {}, 'default'> = { actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', minimumLicenseRequired: 'basic', + isExportable: true, async executor() {}, producer: 'alerts', }; @@ -47,6 +48,7 @@ export const alwaysFiringAlertType: AlertType< defaultActionGroupId: 'default', producer: 'alerts', minimumLicenseRequired: 'basic', + isExportable: true, async executor(alertExecutorOptions) { const { services, state, params } = alertExecutorOptions; @@ -76,6 +78,7 @@ export const failingAlertType: AlertType Date: Tue, 29 Jun 2021 04:19:02 +0300 Subject: [PATCH 41/74] [Osquery] Add Saved queries (#100965) --- .../osquery/common/schemas/common/schemas.ts | 19 +- .../create_saved_query_request_schema.ts | 14 +- x-pack/plugins/osquery/kibana.json | 3 +- .../osquery/public/actions/actions_table.tsx | 19 +- .../public/common/hooks/use_breadcrumbs.tsx | 34 +++ .../osquery/public/common/page_paths.ts | 10 +- .../plugins/osquery/public/components/app.tsx | 9 + .../public/components/osquery_schema_link.tsx | 20 ++ .../plugins/osquery/public/editor/index.tsx | 2 +- .../public/editor/osquery_schema/v4.8.0.json | 1 + .../osquery/public/editor/osquery_tables.ts | 2 +- ...managed_policy_create_import_extension.tsx | 3 +- .../public/live_queries/form/index.tsx | 54 +++- .../form/live_query_query_field.tsx | 19 +- .../common/add_new_pack_query_flyout.tsx | 7 +- .../osquery/public/queries/edit/index.tsx | 53 ---- .../osquery/public/queries/form/index.tsx | 72 ------ .../osquery/public/queries/form/schema.ts | 30 --- .../plugins/osquery/public/queries/index.tsx | 36 --- .../osquery/public/queries/new/index.tsx | 32 --- .../osquery/public/queries/queries/index.tsx | 244 ------------------ .../plugins/osquery/public/routes/index.tsx | 4 + .../routes/live_queries/details/index.tsx | 2 +- .../public/routes/saved_queries/edit/form.tsx | 81 ++++++ .../routes/saved_queries/edit/index.tsx | 124 +++++++++ .../public/routes/saved_queries/edit/tabs.tsx | 78 ++++++ .../public/routes/saved_queries/index.tsx | 35 +++ .../routes/saved_queries/list/index.tsx | 217 ++++++++++++++++ .../public/routes/saved_queries/new/form.tsx | 81 ++++++ .../public/routes/saved_queries/new/index.tsx | 62 +++++ .../osquery/public/saved_queries/constants.ts | 8 + .../form/code_editor_field.tsx | 17 +- .../public/saved_queries/form/index.tsx | 74 ++++++ .../form/use_saved_query_form.tsx | 64 +++++ .../osquery/public/saved_queries/index.tsx | 12 + .../saved_queries/saved_queries_dropdown.tsx | 104 ++++++++ .../saved_queries/saved_query_flyout.tsx | 89 +++++++ .../saved_queries/use_create_saved_query.ts | 70 +++++ .../saved_queries/use_delete_saved_query.ts | 46 ++++ .../public/saved_queries/use_saved_queries.ts | 46 ++++ .../public/saved_queries/use_saved_query.ts | 54 ++++ .../use_scheduled_query_group.ts | 38 +++ .../saved_queries/use_update_saved_query.ts | 66 +++++ .../queries/constants.ts | 3 + .../queries/platform_checkbox_group_field.tsx | 11 +- .../queries/query_flyout.tsx | 48 +++- .../scheduled_query_groups/queries/schema.tsx | 10 + .../use_scheduled_query_group_query_form.tsx | 3 + .../scheduled_query_groups_table.tsx | 25 +- x-pack/plugins/osquery/public/types.ts | 4 +- .../scripts/schema_formatter/script.ts | 2 +- x-pack/plugins/osquery/server/config.ts | 2 +- .../lib/osquery_app_context_services.ts | 3 +- .../lib/saved_query/saved_object_mappings.ts | 30 ++- x-pack/plugins/osquery/server/plugin.ts | 1 + .../routes/action/create_action_route.ts | 6 +- .../saved_query/create_saved_query_route.ts | 6 +- .../saved_query/read_saved_query_route.ts | 8 +- .../saved_query/update_saved_query_route.ts | 6 +- x-pack/plugins/osquery/server/types.ts | 2 + .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 62 files changed, 1678 insertions(+), 551 deletions(-) create mode 100644 x-pack/plugins/osquery/public/components/osquery_schema_link.tsx create mode 100644 x-pack/plugins/osquery/public/editor/osquery_schema/v4.8.0.json delete mode 100644 x-pack/plugins/osquery/public/queries/edit/index.tsx delete mode 100644 x-pack/plugins/osquery/public/queries/form/index.tsx delete mode 100644 x-pack/plugins/osquery/public/queries/form/schema.ts delete mode 100644 x-pack/plugins/osquery/public/queries/index.tsx delete mode 100644 x-pack/plugins/osquery/public/queries/new/index.tsx delete mode 100644 x-pack/plugins/osquery/public/queries/queries/index.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/index.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx create mode 100644 x-pack/plugins/osquery/public/routes/saved_queries/new/index.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/constants.ts rename x-pack/plugins/osquery/public/{queries => saved_queries}/form/code_editor_field.tsx (69%) create mode 100644 x-pack/plugins/osquery/public/saved_queries/form/index.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/index.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_delete_saved_query.ts create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_scheduled_query_group.ts create mode 100644 x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts diff --git a/x-pack/plugins/osquery/common/schemas/common/schemas.ts b/x-pack/plugins/osquery/common/schemas/common/schemas.ts index f5d0a357b85b8..1e52080debb9a 100644 --- a/x-pack/plugins/osquery/common/schemas/common/schemas.ts +++ b/x-pack/plugins/osquery/common/schemas/common/schemas.ts @@ -7,10 +7,10 @@ import * as t from 'io-ts'; -export const name = t.string; -export type Name = t.TypeOf; -export const nameOrUndefined = t.union([name, t.undefined]); -export type NameOrUndefined = t.TypeOf; +export const id = t.string; +export type Id = t.TypeOf; +export const idOrUndefined = t.union([id, t.undefined]); +export type IdOrUndefined = t.TypeOf; export const agentSelection = t.type({ agents: t.array(t.string), @@ -18,6 +18,7 @@ export const agentSelection = t.type({ platformsSelected: t.array(t.string), policiesSelected: t.array(t.string), }); + export type AgentSelection = t.TypeOf; export const agentSelectionOrUndefined = t.union([agentSelection, t.undefined]); export type AgentSelectionOrUndefined = t.TypeOf; @@ -36,3 +37,13 @@ export const query = t.string; export type Query = t.TypeOf; export const queryOrUndefined = t.union([query, t.undefined]); export type QueryOrUndefined = t.TypeOf; + +export const version = t.string; +export type Version = t.TypeOf; +export const versionOrUndefined = t.union([version, t.undefined]); +export type VersionOrUndefined = t.TypeOf; + +export const interval = t.string; +export type Interval = t.TypeOf; +export const intervalOrUndefined = t.union([interval, t.undefined]); +export type IntervalOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts b/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts index 9e901be1476bc..5aa08d9afde4f 100644 --- a/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts +++ b/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts @@ -7,14 +7,24 @@ import * as t from 'io-ts'; -import { name, description, Description, platform, query } from '../../common/schemas'; +import { + id, + description, + Description, + platform, + query, + version, + interval, +} from '../../common/schemas'; import { RequiredKeepUndefined } from '../../../types'; export const createSavedQueryRequestSchema = t.type({ - name, + id, description, platform, query, + version, + interval, }); export type CreateSavedQueryRequestSchema = t.OutputOf; diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index 86a4d817de40e..a8f3975430e5e 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -26,7 +26,8 @@ "features", "fleet", "navigation", - "triggersActionsUi" + "triggersActionsUi", + "security" ], "server": true, "ui": true, diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index 5d1b9b723d98b..0ee928ad8aa14 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isArray } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiButtonIcon, EuiCodeBlock, formatDate } from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; @@ -54,6 +55,8 @@ const ActionsTableComponent = () => { const renderAgentsColumn = useCallback((_, item) => <>{item.fields.agents?.length ?? 0}, []); + const renderCreatedByColumn = useCallback((userId) => (isArray(userId) ? userId[0] : '-'), []); + const renderTimestampColumn = useCallback( (_, item) => <>{formatDate(item.fields['@timestamp'][0])}, [] @@ -90,6 +93,14 @@ const ActionsTableComponent = () => { width: '200px', render: renderTimestampColumn, }, + { + field: 'fields.user_id', + name: i18n.translate('xpack.osquery.liveQueryActions.table.createdByColumnTitle', { + defaultMessage: 'Run by', + }), + width: '200px', + render: renderCreatedByColumn, + }, { name: i18n.translate('xpack.osquery.liveQueryActions.table.viewDetailsColumnTitle', { defaultMessage: 'View details', @@ -101,7 +112,13 @@ const ActionsTableComponent = () => { ], }, ], - [renderActionsColumn, renderAgentsColumn, renderQueryColumn, renderTimestampColumn] + [ + renderActionsColumn, + renderAgentsColumn, + renderCreatedByColumn, + renderQueryColumn, + renderTimestampColumn, + ] ); const pagination = useMemo( diff --git a/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx b/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx index 660ef87fb57e3..7b52b330d0148 100644 --- a/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/osquery/public/common/hooks/use_breadcrumbs.tsx @@ -67,6 +67,40 @@ const breadcrumbGetters: { text: liveQueryId, }, ], + saved_queries: () => [ + BASE_BREADCRUMB, + { + text: i18n.translate('xpack.osquery.breadcrumbs.savedQueriesPageTitle', { + defaultMessage: 'Saved queries', + }), + }, + ], + saved_query_new: () => [ + BASE_BREADCRUMB, + { + href: pagePathGetters.saved_queries(), + text: i18n.translate('xpack.osquery.breadcrumbs.savedQueriesPageTitle', { + defaultMessage: 'Saved queries', + }), + }, + { + text: i18n.translate('xpack.osquery.breadcrumbs.newSavedQueryPageTitle', { + defaultMessage: 'New', + }), + }, + ], + saved_query_edit: ({ savedQueryName }) => [ + BASE_BREADCRUMB, + { + href: pagePathGetters.saved_queries(), + text: i18n.translate('xpack.osquery.breadcrumbs.savedQueriesPageTitle', { + defaultMessage: 'Saved queries', + }), + }, + { + text: savedQueryName, + }, + ], scheduled_query_groups: () => [ BASE_BREADCRUMB, { diff --git a/x-pack/plugins/osquery/public/common/page_paths.ts b/x-pack/plugins/osquery/public/common/page_paths.ts index b4c7963fb9a02..0e0d8310ae8be 100644 --- a/x-pack/plugins/osquery/public/common/page_paths.ts +++ b/x-pack/plugins/osquery/public/common/page_paths.ts @@ -11,12 +11,15 @@ export type StaticPage = | 'live_queries' | 'live_query_new' | 'scheduled_query_groups' - | 'scheduled_query_group_add'; + | 'scheduled_query_group_add' + | 'saved_queries' + | 'saved_query_new'; export type DynamicPage = | 'live_query_details' | 'scheduled_query_group_details' - | 'scheduled_query_group_edit'; + | 'scheduled_query_group_edit' + | 'saved_query_edit'; export type Page = StaticPage | DynamicPage; @@ -50,6 +53,9 @@ export const pagePathGetters: { live_queries: () => '/live_queries', live_query_new: () => '/live_queries/new', live_query_details: ({ liveQueryId }) => `/live_queries/${liveQueryId}`, + saved_queries: () => '/saved_queries', + saved_query_new: () => '/saved_queries/new', + saved_query_edit: ({ savedQueryId }) => `/saved_queries/${savedQueryId}`, scheduled_query_groups: () => '/scheduled_query_groups', scheduled_query_group_add: () => '/scheduled_query_groups/add', scheduled_query_group_details: ({ scheduledQueryGroupId }) => diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index d56aacc99ad53..df61b116a5647 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -50,6 +50,15 @@ const OsqueryAppComponent = () => { defaultMessage="Scheduled query groups" /> + + + diff --git a/x-pack/plugins/osquery/public/components/osquery_schema_link.tsx b/x-pack/plugins/osquery/public/components/osquery_schema_link.tsx new file mode 100644 index 0000000000000..d1f346bad3350 --- /dev/null +++ b/x-pack/plugins/osquery/public/components/osquery_schema_link.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiText, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +export const OsquerySchemaLink = React.memo(() => ( + + + + + +)); + +OsquerySchemaLink.displayName = 'OsquerySchemaLink'; diff --git a/x-pack/plugins/osquery/public/editor/index.tsx b/x-pack/plugins/osquery/public/editor/index.tsx index 70da55ca3f007..5be2b1816ad86 100644 --- a/x-pack/plugins/osquery/public/editor/index.tsx +++ b/x-pack/plugins/osquery/public/editor/index.tsx @@ -40,7 +40,7 @@ const OsqueryEditorComponent: React.FC = ({ name="osquery_editor" setOptions={EDITOR_SET_OPTIONS} editorProps={EDITOR_PROPS} - height="200px" + height="150px" width="100%" /> ); diff --git a/x-pack/plugins/osquery/public/editor/osquery_schema/v4.8.0.json b/x-pack/plugins/osquery/public/editor/osquery_schema/v4.8.0.json new file mode 100644 index 0000000000000..2a15d54e7d323 --- /dev/null +++ b/x-pack/plugins/osquery/public/editor/osquery_schema/v4.8.0.json @@ -0,0 +1 @@ +[{"name":"account_policy_data"},{"name":"acpi_tables"},{"name":"ad_config"},{"name":"alf"},{"name":"alf_exceptions"},{"name":"alf_explicit_auths"},{"name":"app_schemes"},{"name":"apparmor_events"},{"name":"apparmor_profiles"},{"name":"appcompat_shims"},{"name":"apps"},{"name":"apt_sources"},{"name":"arp_cache"},{"name":"asl"},{"name":"atom_packages"},{"name":"augeas"},{"name":"authenticode"},{"name":"authorization_mechanisms"},{"name":"authorizations"},{"name":"authorized_keys"},{"name":"autoexec"},{"name":"azure_instance_metadata"},{"name":"azure_instance_tags"},{"name":"background_activities_moderator"},{"name":"battery"},{"name":"bitlocker_info"},{"name":"block_devices"},{"name":"bpf_process_events"},{"name":"bpf_socket_events"},{"name":"browser_plugins"},{"name":"carbon_black_info"},{"name":"carves"},{"name":"certificates"},{"name":"chassis_info"},{"name":"chocolatey_packages"},{"name":"chrome_extension_content_scripts"},{"name":"chrome_extensions"},{"name":"connectivity"},{"name":"cpu_info"},{"name":"cpu_time"},{"name":"cpuid"},{"name":"crashes"},{"name":"crontab"},{"name":"cups_destinations"},{"name":"cups_jobs"},{"name":"curl"},{"name":"curl_certificate"},{"name":"deb_packages"},{"name":"default_environment"},{"name":"device_file"},{"name":"device_firmware"},{"name":"device_hash"},{"name":"device_partitions"},{"name":"disk_encryption"},{"name":"disk_events"},{"name":"disk_info"},{"name":"dns_cache"},{"name":"dns_resolvers"},{"name":"docker_container_fs_changes"},{"name":"docker_container_labels"},{"name":"docker_container_mounts"},{"name":"docker_container_networks"},{"name":"docker_container_ports"},{"name":"docker_container_processes"},{"name":"docker_container_stats"},{"name":"docker_containers"},{"name":"docker_image_history"},{"name":"docker_image_labels"},{"name":"docker_image_layers"},{"name":"docker_images"},{"name":"docker_info"},{"name":"docker_network_labels"},{"name":"docker_networks"},{"name":"docker_version"},{"name":"docker_volume_labels"},{"name":"docker_volumes"},{"name":"drivers"},{"name":"ec2_instance_metadata"},{"name":"ec2_instance_tags"},{"name":"elf_dynamic"},{"name":"elf_info"},{"name":"elf_sections"},{"name":"elf_segments"},{"name":"elf_symbols"},{"name":"etc_hosts"},{"name":"etc_protocols"},{"name":"etc_services"},{"name":"event_taps"},{"name":"example"},{"name":"extended_attributes"},{"name":"fan_speed_sensors"},{"name":"fbsd_kmods"},{"name":"file"},{"name":"file_events"},{"name":"firefox_addons"},{"name":"gatekeeper"},{"name":"gatekeeper_approved_apps"},{"name":"groups"},{"name":"hardware_events"},{"name":"hash"},{"name":"homebrew_packages"},{"name":"hvci_status"},{"name":"ibridge_info"},{"name":"ie_extensions"},{"name":"intel_me_info"},{"name":"interface_addresses"},{"name":"interface_details"},{"name":"interface_ipv6"},{"name":"iokit_devicetree"},{"name":"iokit_registry"},{"name":"iptables"},{"name":"kernel_extensions"},{"name":"kernel_info"},{"name":"kernel_modules"},{"name":"kernel_panics"},{"name":"keychain_acls"},{"name":"keychain_items"},{"name":"known_hosts"},{"name":"kva_speculative_info"},{"name":"last"},{"name":"launchd"},{"name":"launchd_overrides"},{"name":"listening_ports"},{"name":"lldp_neighbors"},{"name":"load_average"},{"name":"location_services"},{"name":"logged_in_users"},{"name":"logical_drives"},{"name":"logon_sessions"},{"name":"lxd_certificates"},{"name":"lxd_cluster"},{"name":"lxd_cluster_members"},{"name":"lxd_images"},{"name":"lxd_instance_config"},{"name":"lxd_instance_devices"},{"name":"lxd_instances"},{"name":"lxd_networks"},{"name":"lxd_storage_pools"},{"name":"magic"},{"name":"managed_policies"},{"name":"md_devices"},{"name":"md_drives"},{"name":"md_personalities"},{"name":"mdfind"},{"name":"mdls"},{"name":"memory_array_mapped_addresses"},{"name":"memory_arrays"},{"name":"memory_device_mapped_addresses"},{"name":"memory_devices"},{"name":"memory_error_info"},{"name":"memory_info"},{"name":"memory_map"},{"name":"mounts"},{"name":"msr"},{"name":"nfs_shares"},{"name":"npm_packages"},{"name":"ntdomains"},{"name":"ntfs_acl_permissions"},{"name":"ntfs_journal_events"},{"name":"nvram"},{"name":"oem_strings"},{"name":"office_mru"},{"name":"os_version"},{"name":"osquery_events"},{"name":"osquery_extensions"},{"name":"osquery_flags"},{"name":"osquery_info"},{"name":"osquery_packs"},{"name":"osquery_registry"},{"name":"osquery_schedule"},{"name":"package_bom"},{"name":"package_install_history"},{"name":"package_receipts"},{"name":"patches"},{"name":"pci_devices"},{"name":"physical_disk_performance"},{"name":"pipes"},{"name":"pkg_packages"},{"name":"platform_info"},{"name":"plist"},{"name":"portage_keywords"},{"name":"portage_packages"},{"name":"portage_use"},{"name":"power_sensors"},{"name":"powershell_events"},{"name":"preferences"},{"name":"process_envs"},{"name":"process_events"},{"name":"process_file_events"},{"name":"process_memory_map"},{"name":"process_namespaces"},{"name":"process_open_files"},{"name":"process_open_pipes"},{"name":"process_open_sockets"},{"name":"processes"},{"name":"programs"},{"name":"prometheus_metrics"},{"name":"python_packages"},{"name":"quicklook_cache"},{"name":"registry"},{"name":"routes"},{"name":"rpm_package_files"},{"name":"rpm_packages"},{"name":"running_apps"},{"name":"safari_extensions"},{"name":"sandboxes"},{"name":"scheduled_tasks"},{"name":"screenlock"},{"name":"seccomp_events"},{"name":"selinux_events"},{"name":"selinux_settings"},{"name":"services"},{"name":"shadow"},{"name":"shared_folders"},{"name":"shared_memory"},{"name":"shared_resources"},{"name":"sharing_preferences"},{"name":"shell_history"},{"name":"shellbags"},{"name":"shimcache"},{"name":"shortcut_files"},{"name":"signature"},{"name":"sip_config"},{"name":"smart_drive_info"},{"name":"smbios_tables"},{"name":"smc_keys"},{"name":"socket_events"},{"name":"ssh_configs"},{"name":"startup_items"},{"name":"sudoers"},{"name":"suid_bin"},{"name":"syslog_events"},{"name":"system_controls"},{"name":"system_extensions"},{"name":"system_info"},{"name":"systemd_units"},{"name":"temperature_sensors"},{"name":"time"},{"name":"time_machine_backups"},{"name":"time_machine_destinations"},{"name":"ulimit_info"},{"name":"uptime"},{"name":"usb_devices"},{"name":"user_events"},{"name":"user_groups"},{"name":"user_interaction_events"},{"name":"user_ssh_keys"},{"name":"userassist"},{"name":"users"},{"name":"video_info"},{"name":"virtual_memory_info"},{"name":"wifi_networks"},{"name":"wifi_status"},{"name":"wifi_survey"},{"name":"winbaseobj"},{"name":"windows_crashes"},{"name":"windows_eventlog"},{"name":"windows_events"},{"name":"windows_optional_features"},{"name":"windows_security_center"},{"name":"windows_security_products"},{"name":"wmi_bios_info"},{"name":"wmi_cli_event_consumers"},{"name":"wmi_event_filters"},{"name":"wmi_filter_consumer_binding"},{"name":"wmi_script_event_consumers"},{"name":"xprotect_entries"},{"name":"xprotect_meta"},{"name":"xprotect_reports"},{"name":"yara"},{"name":"yara_events"},{"name":"ycloud_instance_metadata"},{"name":"yum_sources"}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/editor/osquery_tables.ts b/x-pack/plugins/osquery/public/editor/osquery_tables.ts index d114cda742f9d..d41df4021bae6 100644 --- a/x-pack/plugins/osquery/public/editor/osquery_tables.ts +++ b/x-pack/plugins/osquery/public/editor/osquery_tables.ts @@ -20,7 +20,7 @@ let osqueryTables: TablesJSON | null = null; export const getOsqueryTables = () => { if (!osqueryTables) { // eslint-disable-next-line @typescript-eslint/no-var-requires - osqueryTables = normalizeTables(require('./osquery_schema/v4.7.0.json')); + osqueryTables = normalizeTables(require('./osquery_schema/v4.8.0.json')); } return osqueryTables; }; diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx index 2305df807f1c8..28d69a6a7b15a 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx @@ -207,7 +207,8 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< integrationPolicyId={policy?.id} agentPolicyId={policy?.policy_id} /> - + + {editMode && scheduledQueryGroupTableData.inputs[0].streams.length ? ( diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 6f2d1afec6fe9..9e952810e3352 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -5,20 +5,28 @@ * 2.0. */ -import { EuiButton, EuiSteps, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiSteps, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useMutation } from 'react-query'; import { UseField, Form, FormData, useForm, useFormData, FIELD_TYPES } from '../../shared_imports'; import { AgentsTableField } from './agents_table_field'; import { LiveQueryQueryField } from './live_query_query_field'; import { useKibana } from '../../common/lib/kibana'; -import { ResultTabs } from '../../queries/edit/tabs'; +import { ResultTabs } from '../../routes/saved_queries/edit/tabs'; import { queryFieldValidation } from '../../common/validations'; import { fieldValidators } from '../../shared_imports'; +import { SavedQueryFlyout } from '../../saved_queries'; import { useErrorToast } from '../../common/hooks/use_error_toast'; const FORM_ID = 'liveQueryForm'; @@ -27,19 +35,17 @@ export const MAX_QUERY_LENGTH = 2000; interface LiveQueryFormProps { defaultValue?: Partial | undefined; - onSubmit?: (payload: Record) => Promise; onSuccess?: () => void; } -const LiveQueryFormComponent: React.FC = ({ - defaultValue, - // onSubmit, - onSuccess, -}) => { +const LiveQueryFormComponent: React.FC = ({ defaultValue, onSuccess }) => { const { http } = useKibana().services; - + const [showSavedQueryFlyout, setShowSavedQueryFlyout] = useState(false); const setErrorToast = useErrorToast(); + const handleShowSaveQueryFlout = useCallback(() => setShowSavedQueryFlyout(true), []); + const handleCloseSaveQueryFlout = useCallback(() => setShowSavedQueryFlyout(false), []); + const { data, isLoading, @@ -139,6 +145,8 @@ const LiveQueryFormComponent: React.FC = ({ [queryStatus] ); + const flyoutFormDefaultValue = useMemo(() => ({ query }), [query]); + const formSteps: EuiContainedStepProps[] = useMemo( () => [ { @@ -161,6 +169,17 @@ const LiveQueryFormComponent: React.FC = ({ /> + + + + + = ({ actionId, agentIds, agentSelected, + handleShowSaveQueryFlout, queryComponentProps, queryStatus, queryValueProvided, @@ -203,9 +223,17 @@ const LiveQueryFormComponent: React.FC = ({ ); return ( -
    - - + <> +
    + + + {showSavedQueryFlyout ? ( + + ) : null} + ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx index 07c13b930e143..9f0b5acd8994a 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/live_query_query_field.tsx @@ -5,11 +5,13 @@ * 2.0. */ +import { EuiFormRow, EuiSpacer } from '@elastic/eui'; import React, { useCallback } from 'react'; -import { EuiFormRow } from '@elastic/eui'; +import { OsquerySchemaLink } from '../../components/osquery_schema_link'; import { FieldHook } from '../../shared_imports'; import { OsqueryEditor } from '../../editor'; +import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown'; interface LiveQueryQueryFieldProps { disabled?: boolean; @@ -20,6 +22,13 @@ const LiveQueryQueryFieldComponent: React.FC = ({ disa const { value, setValue, errors } = field; const error = errors[0]?.message; + const handleSavedQueryChange = useCallback( + (savedQuery) => { + setValue(savedQuery.query); + }, + [setValue] + ); + const handleEditorChange = useCallback( (newValue) => { setValue(newValue); @@ -29,7 +38,13 @@ const LiveQueryQueryFieldComponent: React.FC = ({ disa return ( - + <> + + + }> + + + ); }; diff --git a/x-pack/plugins/osquery/public/packs/common/add_new_pack_query_flyout.tsx b/x-pack/plugins/osquery/public/packs/common/add_new_pack_query_flyout.tsx index 2680b5198fadb..85578564b1eb2 100644 --- a/x-pack/plugins/osquery/public/packs/common/add_new_pack_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/packs/common/add_new_pack_query_flyout.tsx @@ -8,7 +8,7 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; import React from 'react'; -import { SavedQueryForm } from '../../queries/form'; +import { SavedQueryForm } from '../../saved_queries/form'; // @ts-expect-error update types const AddNewPackQueryFlyoutComponent = ({ handleClose, handleSubmit }) => ( @@ -19,7 +19,10 @@ const AddNewPackQueryFlyoutComponent = ({ handleClose, handleSubmit }) => ( - + { + // @ts-expect-error update types + + } ); diff --git a/x-pack/plugins/osquery/public/queries/edit/index.tsx b/x-pack/plugins/osquery/public/queries/edit/index.tsx deleted file mode 100644 index 61094b2d07940..0000000000000 --- a/x-pack/plugins/osquery/public/queries/edit/index.tsx +++ /dev/null @@ -1,53 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty } from 'lodash/fp'; -import React from 'react'; -import { useMutation, useQuery } from 'react-query'; - -import { SavedQueryForm } from '../form'; -import { useKibana } from '../../common/lib/kibana'; - -interface EditSavedQueryPageProps { - onSuccess: () => void; - savedQueryId: string; -} - -const EditSavedQueryPageComponent: React.FC = ({ - onSuccess, - savedQueryId, -}) => { - const { http } = useKibana().services; - - const { isLoading, data: savedQueryDetails } = useQuery(['savedQuery', { savedQueryId }], () => - http.get(`/internal/osquery/saved_query/${savedQueryId}`) - ); - const updateSavedQueryMutation = useMutation( - (payload) => - http.put(`/internal/osquery/saved_query/${savedQueryId}`, { body: JSON.stringify(payload) }), - { onSuccess } - ); - - if (isLoading) { - return <>{'Loading...'}; - } - - return ( - <> - {!isEmpty(savedQueryDetails) && ( - - )} - - ); -}; - -export const EditSavedQueryPage = React.memo(EditSavedQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/queries/form/index.tsx b/x-pack/plugins/osquery/public/queries/form/index.tsx deleted file mode 100644 index 02468fbfde228..0000000000000 --- a/x-pack/plugins/osquery/public/queries/form/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButton, EuiSpacer } from '@elastic/eui'; -import React from 'react'; - -import { Field, getUseField, useForm, UseField, Form } from '../../shared_imports'; -import { CodeEditorField } from './code_editor_field'; -import { formSchema } from './schema'; - -export const CommonUseField = getUseField({ component: Field }); - -const SAVED_QUERY_FORM_ID = 'savedQueryForm'; - -interface SavedQueryFormProps { - defaultValue?: unknown; - handleSubmit: () => Promise; - type?: string; -} - -const SavedQueryFormComponent: React.FC = ({ - defaultValue, - handleSubmit, - type, -}) => { - const { form } = useForm({ - // @ts-expect-error update types - id: defaultValue ? SAVED_QUERY_FORM_ID + defaultValue.id : SAVED_QUERY_FORM_ID, - schema: formSchema, - onSubmit: handleSubmit, - options: { - stripEmptyFields: false, - }, - // @ts-expect-error update types - defaultValue, - }); - - const { submit } = form; - - return ( -
    - - - - - - - - - {type === 'edit' ? 'Update' : 'Save'} - - ); -}; - -export const SavedQueryForm = React.memo(SavedQueryFormComponent); diff --git a/x-pack/plugins/osquery/public/queries/form/schema.ts b/x-pack/plugins/osquery/public/queries/form/schema.ts deleted file mode 100644 index 33200e45dc8e3..0000000000000 --- a/x-pack/plugins/osquery/public/queries/form/schema.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FIELD_TYPES, FormSchema } from '../../shared_imports'; - -export const formSchema: FormSchema = { - name: { - type: FIELD_TYPES.TEXT, - label: 'Query name', - }, - description: { - type: FIELD_TYPES.TEXTAREA, - label: 'Description', - validations: [], - }, - platform: { - type: FIELD_TYPES.SELECT, - label: 'Platform', - defaultValue: 'all', - }, - query: { - label: 'Query', - type: FIELD_TYPES.TEXTAREA, - validations: [], - }, -}; diff --git a/x-pack/plugins/osquery/public/queries/index.tsx b/x-pack/plugins/osquery/public/queries/index.tsx deleted file mode 100644 index 7ecce3cfb22f4..0000000000000 --- a/x-pack/plugins/osquery/public/queries/index.tsx +++ /dev/null @@ -1,36 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useState } from 'react'; - -import { QueriesPage } from './queries'; -import { NewSavedQueryPage } from './new'; -import { EditSavedQueryPage } from './edit'; - -const QueriesComponent = () => { - const [showNewSavedQueryForm, setShowNewSavedQueryForm] = useState(false); - const [editSavedQueryId, setEditSavedQueryId] = useState(null); - - const goBack = useCallback(() => { - setShowNewSavedQueryForm(false); - setEditSavedQueryId(null); - }, []); - - const handleNewQueryClick = useCallback(() => setShowNewSavedQueryForm(true), []); - - if (showNewSavedQueryForm) { - return ; - } - - if (editSavedQueryId?.length) { - return ; - } - - return ; -}; - -export const Queries = React.memo(QueriesComponent); diff --git a/x-pack/plugins/osquery/public/queries/new/index.tsx b/x-pack/plugins/osquery/public/queries/new/index.tsx deleted file mode 100644 index 2682db126ea09..0000000000000 --- a/x-pack/plugins/osquery/public/queries/new/index.tsx +++ /dev/null @@ -1,32 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { useMutation } from 'react-query'; - -import { useKibana } from '../../common/lib/kibana'; -import { SavedQueryForm } from '../form'; - -interface NewSavedQueryPageProps { - onSuccess: () => void; -} - -const NewSavedQueryPageComponent: React.FC = ({ onSuccess }) => { - const { http } = useKibana().services; - - const createSavedQueryMutation = useMutation( - (payload) => http.post(`/internal/osquery/saved_query`, { body: JSON.stringify(payload) }), - { - onSuccess, - } - ); - - // @ts-expect-error update types - return ; -}; - -export const NewSavedQueryPage = React.memo(NewSavedQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/queries/queries/index.tsx b/x-pack/plugins/osquery/public/queries/queries/index.tsx deleted file mode 100644 index bf766a15a44a3..0000000000000 --- a/x-pack/plugins/osquery/public/queries/queries/index.tsx +++ /dev/null @@ -1,244 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { map } from 'lodash/fp'; -import { - EuiBasicTable, - EuiButton, - EuiButtonIcon, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - RIGHT_ALIGNMENT, -} from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { useQuery, useQueryClient, useMutation } from 'react-query'; -import { useHistory } from 'react-router-dom'; -import qs from 'query-string'; - -import { useKibana } from '../../common/lib/kibana'; - -interface QueriesPageProps { - onEditClick: (savedQueryId: string) => void; - onNewClick: () => void; -} - -const QueriesPageComponent: React.FC = ({ onEditClick, onNewClick }) => { - const { push } = useHistory(); - const queryClient = useQueryClient(); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); - const [sortField, setSortField] = useState('updated_at'); - const [sortDirection, setSortDirection] = useState('desc'); - const [selectedItems, setSelectedItems] = useState([]); - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>({}); - const { http } = useKibana().services; - - const deleteSavedQueriesMutation = useMutation( - (payload) => http.delete(`/internal/osquery/saved_query`, { body: JSON.stringify(payload) }), - { - onSuccess: () => queryClient.invalidateQueries('savedQueryList'), - } - ); - - const { data = {} } = useQuery( - ['savedQueryList', { pageIndex, pageSize, sortField, sortDirection }], - () => - http.get('/internal/osquery/saved_query', { - query: { - pageIndex, - pageSize, - sortField, - sortDirection, - }, - }), - { - keepPreviousData: true, - // Refetch the data every 10 seconds - refetchInterval: 5000, - } - ); - const { total = 0, saved_objects: savedQueries } = data; - - const toggleDetails = useCallback( - (item) => () => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[item.id]) { - delete itemIdToExpandedRowMapValues[item.id]; - } else { - itemIdToExpandedRowMapValues[item.id] = ( - - {item.attributes.query} - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }, - [itemIdToExpandedRowMap] - ); - - const renderExtendedItemToggle = useCallback( - (item) => ( - - ), - [itemIdToExpandedRowMap, toggleDetails] - ); - - const handleEditClick = useCallback((item) => onEditClick(item.id), [onEditClick]); - - const handlePlayClick = useCallback( - (item) => - push({ - search: qs.stringify({ - tab: 'live_query', - }), - state: { - query: { - id: item.id, - query: item.attributes.query, - }, - }, - }), - [push] - ); - - const columns = useMemo( - () => [ - { - field: 'attributes.name', - name: 'Query name', - sortable: true, - truncateText: true, - }, - { - field: 'attributes.description', - name: 'Description', - sortable: true, - truncateText: true, - }, - { - field: 'updated_at', - name: 'Last updated at', - sortable: true, - truncateText: true, - }, - { - name: 'Actions', - actions: [ - { - name: 'Live query', - description: 'Run live query', - type: 'icon', - icon: 'play', - onClick: handlePlayClick, - }, - { - name: 'Edit', - description: 'Edit or run this query', - type: 'icon', - icon: 'documentEdit', - onClick: handleEditClick, - }, - ], - }, - { - align: RIGHT_ALIGNMENT, - width: '40px', - isExpander: true, - render: renderExtendedItemToggle, - }, - ], - [handleEditClick, handlePlayClick, renderExtendedItemToggle] - ); - - const onTableChange = useCallback(({ page = {}, sort = {} }) => { - setPageIndex(page.index); - setPageSize(page.size); - setSortField(sort.field); - setSortDirection(sort.direction); - }, []); - - const pagination = useMemo( - () => ({ - pageIndex, - pageSize, - totalItemCount: total, - pageSizeOptions: [3, 5, 8], - }), - [total, pageIndex, pageSize] - ); - - const sorting = useMemo( - () => ({ - sort: { - field: sortField, - direction: sortDirection, - }, - }), - [sortDirection, sortField] - ); - - const selection = useMemo( - () => ({ - selectable: () => true, - onSelectionChange: setSelectedItems, - initialSelected: [], - }), - [] - ); - - const handleDeleteClick = useCallback(() => { - const selectedItemsIds = map('id', selectedItems); - // @ts-expect-error update types - deleteSavedQueriesMutation.mutate({ savedQueryIds: selectedItemsIds }); - }, [deleteSavedQueriesMutation, selectedItems]); - - return ( -
    - - - {!selectedItems.length ? ( - - {'New query'} - - ) : ( - - {`Delete ${selectedItems.length} Queries`} - - )} - - - - - - {savedQueries && ( - - )} -
    - ); -}; - -export const QueriesPage = React.memo(QueriesPageComponent); diff --git a/x-pack/plugins/osquery/public/routes/index.tsx b/x-pack/plugins/osquery/public/routes/index.tsx index 7007feb19d663..a858a51aad64e 100644 --- a/x-pack/plugins/osquery/public/routes/index.tsx +++ b/x-pack/plugins/osquery/public/routes/index.tsx @@ -11,12 +11,16 @@ import { Switch, Redirect, Route } from 'react-router-dom'; import { useBreadcrumbs } from '../common/hooks/use_breadcrumbs'; import { LiveQueries } from './live_queries'; import { ScheduledQueryGroups } from './scheduled_query_groups'; +import { SavedQueries } from './saved_queries'; const OsqueryAppRoutesComponent = () => { useBreadcrumbs('base'); return ( + + + diff --git a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx index 64a1fb0791e83..e4f1bb447a15a 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx @@ -27,7 +27,7 @@ import { useRouterNavigate } from '../../../common/lib/kibana'; import { WithHeaderLayout } from '../../../components/layouts'; import { useActionResults } from '../../../action_results/use_action_results'; import { useActionDetails } from '../../../actions/use_action_details'; -import { ResultTabs } from '../../../queries/edit/tabs'; +import { ResultTabs } from '../../saved_queries/edit/tabs'; import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx new file mode 100644 index 0000000000000..8d77b7819bd3e --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBottomBar, + EuiButtonEmpty, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useRouterNavigate } from '../../../common/lib/kibana'; +import { Form } from '../../../shared_imports'; +import { SavedQueryForm } from '../../../saved_queries/form'; +import { useSavedQueryForm } from '../../../saved_queries/form/use_saved_query_form'; + +interface EditSavedQueryFormProps { + defaultValue?: unknown; + handleSubmit: () => Promise; +} + +const EditSavedQueryFormComponent: React.FC = ({ + defaultValue, + handleSubmit, +}) => { + const savedQueryListProps = useRouterNavigate('saved_queries'); + + const { form } = useSavedQueryForm({ + defaultValue, + handleSubmit, + }); + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const EditSavedQueryForm = React.memo(EditSavedQueryFormComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx new file mode 100644 index 0000000000000..4aaf8e4fc4fc3 --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonEmpty, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiConfirmModal, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { useCallback, useMemo, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useParams } from 'react-router-dom'; + +import { useRouterNavigate } from '../../../common/lib/kibana'; +import { WithHeaderLayout } from '../../../components/layouts'; +import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; +import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; +import { EditSavedQueryForm } from './form'; +import { useDeleteSavedQuery, useUpdateSavedQuery, useSavedQuery } from '../../../saved_queries'; + +const EditSavedQueryPageComponent = () => { + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const { savedQueryId } = useParams<{ savedQueryId: string }>(); + const savedQueryListProps = useRouterNavigate('saved_queries'); + + const { isLoading, data: savedQueryDetails } = useSavedQuery({ savedQueryId }); + const updateSavedQueryMutation = useUpdateSavedQuery({ savedQueryId }); + const deleteSavedQueryMutation = useDeleteSavedQuery({ savedQueryId }); + + useBreadcrumbs('saved_query_edit', { savedQueryId: savedQueryDetails?.attributes?.id ?? '' }); + + const handleCloseDeleteConfirmationModal = useCallback(() => { + setIsDeleteModalVisible(false); + }, []); + + const handleDeleteClick = useCallback(() => { + setIsDeleteModalVisible(true); + }, []); + + const handleDeleteConfirmClick = useCallback(() => { + deleteSavedQueryMutation.mutateAsync().then(() => { + handleCloseDeleteConfirmationModal(); + }); + }, [deleteSavedQueryMutation, handleCloseDeleteConfirmationModal]); + + const LeftColumn = useMemo( + () => ( + + + + + + + + +

    + +

    + +
    +
    +
    + ), + [savedQueryDetails?.attributes?.id, savedQueryListProps] + ); + + const RightColumn = useMemo( + () => ( + + + + ), + [handleDeleteClick] + ); + + if (isLoading) return null; + + return ( + + {!isLoading && !isEmpty(savedQueryDetails) && ( + + )} + {isDeleteModalVisible ? ( + +

    You’re about to delete this query.

    +

    Are you sure you want to do this?

    +
    + ) : null} +
    + ); +}; + +export const EditSavedQueryPage = React.memo(EditSavedQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx new file mode 100644 index 0000000000000..1946cd6dd3450 --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiTabbedContent, EuiSpacer } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { ResultsTable } from '../../../results/results_table'; +import { ActionResultsSummary } from '../../../action_results/action_results_summary'; + +interface ResultTabsProps { + actionId: string; + agentIds?: string[]; + expirationDate: Date; + isLive?: boolean; + startDate?: string; + endDate?: string; +} + +const ResultTabsComponent: React.FC = ({ + actionId, + agentIds, + endDate, + expirationDate, + isLive, + startDate, +}) => { + const tabs = useMemo( + () => [ + { + id: 'results', + name: 'Results', + content: ( + <> + + + + ), + }, + { + id: 'status', + name: 'Status', + content: ( + <> + + + + ), + }, + ], + [actionId, agentIds, endDate, expirationDate, isLive, startDate] + ); + + return ( + + ); +}; + +export const ResultTabs = React.memo(ResultTabsComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/index.tsx new file mode 100644 index 0000000000000..f986129bdfefc --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Switch, Route, useRouteMatch } from 'react-router-dom'; + +import { QueriesPage } from './list'; +import { NewSavedQueryPage } from './new'; +import { EditSavedQueryPage } from './edit'; +import { useBreadcrumbs } from '../../common/hooks/use_breadcrumbs'; + +const SavedQueriesComponent = () => { + useBreadcrumbs('saved_queries'); + const match = useRouteMatch(); + + return ( + + + + + + + + + + + + ); +}; + +export const SavedQueries = React.memo(SavedQueriesComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx new file mode 100644 index 0000000000000..7e8e8e543dfab --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { + EuiInMemoryTable, + EuiButton, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { SavedObject } from 'kibana/public'; +import { WithHeaderLayout } from '../../../components/layouts'; +import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; +import { useRouterNavigate } from '../../../common/lib/kibana'; +import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; +import { useSavedQueries } from '../../../saved_queries/use_saved_queries'; + +interface EditButtonProps { + savedQueryId: string; + savedQueryName: string; +} + +const EditButtonComponent: React.FC = ({ savedQueryId, savedQueryName }) => { + const buttonProps = useRouterNavigate(`saved_queries/${savedQueryId}`); + + return ( + + ); +}; + +const EditButton = React.memo(EditButtonComponent); + +const SavedQueriesPageComponent = () => { + useBreadcrumbs('saved_queries'); + const newQueryLinkProps = useRouterNavigate('saved_queries/new'); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState('updated_at'); + const [sortDirection, setSortDirection] = useState('desc'); + + const { data } = useSavedQueries({ isLive: true }); + + // const handlePlayClick = useCallback( + // (item) => + // push({ + // search: qs.stringify({ + // tab: 'live_query', + // }), + // state: { + // query: { + // id: item.id, + // query: item.attributes.query, + // }, + // }, + // }), + // [push] + // ); + + const renderEditAction = useCallback( + (item: SavedObject<{ name: string }>) => ( + + ), + [] + ); + + const renderUpdatedAt = useCallback((updatedAt, item) => { + if (!updatedAt) return '-'; + + const updatedBy = + item.attributes.updated_by !== item.attributes.created_by + ? ` @ ${item.attributes.updated_by}` + : ''; + return updatedAt ? `${moment(updatedAt).fromNow()}${updatedBy}` : '-'; + }, []); + + const columns = useMemo( + () => [ + { + field: 'attributes.id', + name: 'Query ID', + sortable: true, + truncateText: true, + }, + { + field: 'attributes.description', + name: 'Description', + sortable: true, + truncateText: true, + }, + { + field: 'attributes.created_by', + name: 'Created by', + sortable: true, + truncateText: true, + }, + { + field: 'attributes.updated_at', + name: 'Last updated at', + sortable: (item: SavedObject<{ updated_at: string }>) => + item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0, + truncateText: true, + render: renderUpdatedAt, + }, + { + name: 'Actions', + actions: [ + // { + // name: 'Live query', + // description: 'Run live query', + // type: 'icon', + // icon: 'play', + // onClick: handlePlayClick, + // }, + { render: renderEditAction }, + ], + }, + ], + [renderEditAction, renderUpdatedAt] + ); + + const onTableChange = useCallback(({ page = {}, sort = {} }) => { + setPageIndex(page.index); + setPageSize(page.size); + setSortField(sort.field); + setSortDirection(sort.direction); + }, []); + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + totalItemCount: data?.total ?? 0, + pageSizeOptions: [10, 20, 50, 100], + }), + [pageIndex, pageSize, data?.total] + ); + + const sorting = useMemo( + () => ({ + sort: { + field: sortField, + direction: sortDirection, + }, + }), + [sortDirection, sortField] + ); + + const LeftColumn = useMemo( + () => ( + + + +

    + +

    + +
    +
    +
    + ), + [] + ); + + const RightColumn = useMemo( + () => ( + + + + ), + [newQueryLinkProps] + ); + + return ( + + {data?.savedObjects && ( + + )} + + ); +}; + +export const QueriesPage = React.memo(SavedQueriesPageComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx new file mode 100644 index 0000000000000..31d0c5637cc3e --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBottomBar, + EuiButtonEmpty, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useRouterNavigate } from '../../../common/lib/kibana'; +import { Form } from '../../../shared_imports'; +import { SavedQueryForm } from '../../../saved_queries/form'; +import { useSavedQueryForm } from '../../../saved_queries/form/use_saved_query_form'; + +interface NewSavedQueryFormProps { + defaultValue?: unknown; + handleSubmit: () => Promise; +} + +const NewSavedQueryFormComponent: React.FC = ({ + defaultValue, + handleSubmit, +}) => { + const savedQueryListProps = useRouterNavigate('saved_queries'); + + const { form } = useSavedQueryForm({ + defaultValue, + handleSubmit, + }); + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const NewSavedQueryForm = React.memo(NewSavedQueryFormComponent); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/new/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/new/index.tsx new file mode 100644 index 0000000000000..3f5a1af64fe34 --- /dev/null +++ b/x-pack/plugins/osquery/public/routes/saved_queries/new/index.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useRouterNavigate } from '../../../common/lib/kibana'; +import { WithHeaderLayout } from '../../../components/layouts'; +import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; +import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; +import { NewSavedQueryForm } from './form'; +import { useCreateSavedQuery } from '../../../saved_queries/use_create_saved_query'; + +const NewSavedQueryPageComponent = () => { + useBreadcrumbs('saved_query_new'); + const savedQueryListProps = useRouterNavigate('saved_queries'); + + const createSavedQueryMutation = useCreateSavedQuery({ withRedirect: true }); + + const LeftColumn = useMemo( + () => ( + + + + + + + + +

    + +

    + +
    +
    +
    + ), + [savedQueryListProps] + ); + + return ( + + { + // @ts-expect-error update types + + } + + ); +}; + +export const NewSavedQueryPage = React.memo(NewSavedQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/saved_queries/constants.ts b/x-pack/plugins/osquery/public/saved_queries/constants.ts new file mode 100644 index 0000000000000..69ca805e3e8fa --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SAVED_QUERIES_ID = 'savedQueryList'; diff --git a/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx b/x-pack/plugins/osquery/public/saved_queries/form/code_editor_field.tsx similarity index 69% rename from x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx rename to x-pack/plugins/osquery/public/saved_queries/form/code_editor_field.tsx index 77ffdc4457d3d..c70aeae66396e 100644 --- a/x-pack/plugins/osquery/public/queries/form/code_editor_field.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/code_editor_field.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { FormattedMessage } from '@kbn/i18n/react'; import { isEmpty } from 'lodash/fp'; -import { EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; +import { EuiFormRow } from '@elastic/eui'; import React from 'react'; +import { OsquerySchemaLink } from '../../components/osquery_schema_link'; import { OsqueryEditor } from '../../editor'; import { FieldHook } from '../../shared_imports'; @@ -17,19 +17,6 @@ interface CodeEditorFieldProps { field: FieldHook; } -const OsquerySchemaLink = React.memo(() => ( - - - - - -)); - -OsquerySchemaLink.displayName = 'OsquerySchemaLink'; - const CodeEditorFieldComponent: React.FC = ({ field }) => { const { value, label, labelAppend, helpText, setValue, errors } = field; const error = errors[0]?.message; diff --git a/x-pack/plugins/osquery/public/saved_queries/form/index.tsx b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx new file mode 100644 index 0000000000000..174227eb5e6e5 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { ALL_OSQUERY_VERSIONS_OPTIONS } from '../../scheduled_query_groups/queries/constants'; +import { PlatformCheckBoxGroupField } from '../../scheduled_query_groups/queries/platform_checkbox_group_field'; +import { Field, getUseField, UseField } from '../../shared_imports'; +import { CodeEditorField } from './code_editor_field'; + +export const CommonUseField = getUseField({ component: Field }); + +const SavedQueryFormComponent = () => ( + <> + + + + + + + + + +
    + +
    +
    + + + +
    +
    + + + + + + + + + + + + + +); + +export const SavedQueryForm = React.memo(SavedQueryFormComponent); diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx new file mode 100644 index 0000000000000..6417b40747e0f --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isArray } from 'lodash'; +import uuid from 'uuid'; +import { produce } from 'immer'; + +import { useForm } from '../../shared_imports'; +import { formSchema } from '../../scheduled_query_groups/queries/schema'; +import { ScheduledQueryGroupFormData } from '../../scheduled_query_groups/queries/use_scheduled_query_group_query_form'; + +const SAVED_QUERY_FORM_ID = 'savedQueryForm'; + +interface UseSavedQueryFormProps { + defaultValue?: unknown; + handleSubmit: (payload: unknown) => Promise; +} + +export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryFormProps) => + useForm({ + id: SAVED_QUERY_FORM_ID + uuid.v4(), + schema: formSchema, + onSubmit: handleSubmit, + options: { + stripEmptyFields: false, + }, + // @ts-expect-error update types + defaultValue, + serializer: (payload) => + produce(payload, (draft) => { + // @ts-expect-error update types + if (draft.platform?.split(',').length === 3) { + // if all platforms are checked then use undefined + // @ts-expect-error update types + delete draft.platform; + } + if (isArray(draft.version)) { + if (!draft.version.length) { + // @ts-expect-error update types + delete draft.version; + } else { + draft.version = draft.version[0]; + } + } + return draft; + }), + // @ts-expect-error update types + deserializer: (payload) => { + if (!payload) return {} as ScheduledQueryGroupFormData; + + return { + id: payload.id, + description: payload.description, + query: payload.query, + interval: payload.interval ? parseInt(payload.interval, 10) : undefined, + platform: payload.platform, + version: payload.version ? [payload.version] : [], + }; + }, + }); diff --git a/x-pack/plugins/osquery/public/saved_queries/index.tsx b/x-pack/plugins/osquery/public/saved_queries/index.tsx new file mode 100644 index 0000000000000..405af5638c868 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './saved_query_flyout'; +export * from './use_saved_query'; +export * from './use_saved_queries'; +export * from './use_update_saved_query'; +export * from './use_delete_saved_query'; diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx new file mode 100644 index 0000000000000..e30954a695b2d --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { find } from 'lodash/fp'; +import { EuiCodeBlock, EuiFormRow, EuiComboBox, EuiText } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { SimpleSavedObject } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useSavedQueries } from './use_saved_queries'; + +interface SavedQueriesDropdownProps { + disabled?: boolean; + onChange: ( + value: SimpleSavedObject<{ + id: string; + description?: string | undefined; + query: string; + }>['attributes'] + ) => void; +} + +const SavedQueriesDropdownComponent: React.FC = ({ + disabled, + onChange, +}) => { + const [selectedOptions, setSelectedOptions] = useState([]); + + const { data } = useSavedQueries({}); + + const queryOptions = + data?.savedObjects?.map((savedQuery) => ({ + label: savedQuery.attributes.id ?? '', + value: { + id: savedQuery.attributes.id, + description: savedQuery.attributes.description, + query: savedQuery.attributes.query, + }, + })) ?? []; + + const handleSavedQueryChange = useCallback( + (newSelectedOptions) => { + const selectedSavedQuery = find( + ['attributes.id', newSelectedOptions[0].value.id], + data?.savedObjects + ); + + if (selectedSavedQuery) { + onChange(selectedSavedQuery.attributes); + } + setSelectedOptions(newSelectedOptions); + }, + [data?.savedObjects, onChange] + ); + + const renderOption = useCallback( + ({ value }) => ( + <> + {value.id} + +

    {value.description}

    +
    + + {value.query} + + + ), + [] + ); + + return ( + + } + fullWidth + > + + + ); +}; + +export const SavedQueriesDropdown = React.memo(SavedQueriesDropdownComponent); diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx new file mode 100644 index 0000000000000..6d14943a6bc84 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFlyout, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiPortal, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { Form } from '../shared_imports'; +import { useSavedQueryForm } from './form/use_saved_query_form'; +import { SavedQueryForm } from './form'; +import { useCreateSavedQuery } from './use_create_saved_query'; + +interface AddQueryFlyoutProps { + defaultValue: unknown; + onClose: () => void; +} + +const SavedQueryFlyoutComponent: React.FC = ({ defaultValue, onClose }) => { + const createSavedQueryMutation = useCreateSavedQuery({ withRedirect: false }); + + const handleSubmit = useCallback( + (payload) => createSavedQueryMutation.mutateAsync(payload).then(() => onClose()), + [createSavedQueryMutation, onClose] + ); + + const { form } = useSavedQueryForm({ + defaultValue, + handleSubmit, + }); + + return ( + + + + +

    + +

    +
    +
    + +
    + + +
    + + + + + + + + + + + + + + +
    +
    + ); +}; + +export const SavedQueryFlyout = React.memo(SavedQueryFlyoutComponent); diff --git a/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts new file mode 100644 index 0000000000000..cc5c33c6e4280 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from 'react-query'; +import { i18n } from '@kbn/i18n'; + +import { useKibana } from '../common/lib/kibana'; +import { savedQuerySavedObjectType } from '../../common/types'; +import { PLUGIN_ID } from '../../common'; +import { pagePathGetters } from '../common/page_paths'; +import { SAVED_QUERIES_ID } from './constants'; +import { useErrorToast } from '../common/hooks/use_error_toast'; + +interface UseCreateSavedQueryProps { + withRedirect?: boolean; +} + +export const useCreateSavedQuery = ({ withRedirect }: UseCreateSavedQueryProps) => { + const queryClient = useQueryClient(); + const { + application: { navigateToApp }, + savedObjects, + security, + notifications: { toasts }, + } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useMutation( + async (payload) => { + const currentUser = await security.authc.getCurrentUser(); + + if (!currentUser) { + throw new Error('CurrentUser is missing'); + } + + return savedObjects.client.create(savedQuerySavedObjectType, { + // @ts-expect-error update types + ...payload, + created_by: currentUser.username, + created_at: new Date(Date.now()).toISOString(), + updated_by: currentUser.username, + updated_at: new Date(Date.now()).toISOString(), + }); + }, + { + onError: (error) => { + // @ts-expect-error update types + setErrorToast(error, { title: error.body.error, toastMessage: error.body.message }); + }, + onSuccess: (payload) => { + queryClient.invalidateQueries(SAVED_QUERIES_ID); + if (withRedirect) { + navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); + } + toasts.addSuccess( + i18n.translate('xpack.osquery.newSavedQuery.successToastMessageText', { + defaultMessage: 'Successfully saved "{savedQueryId}" query', + values: { + savedQueryId: payload.attributes?.id ?? '', + }, + }) + ); + }, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_delete_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_delete_saved_query.ts new file mode 100644 index 0000000000000..b2fee8b25f7a4 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_delete_saved_query.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from 'react-query'; +import { i18n } from '@kbn/i18n'; + +import { useKibana } from '../common/lib/kibana'; +import { savedQuerySavedObjectType } from '../../common/types'; +import { PLUGIN_ID } from '../../common'; +import { pagePathGetters } from '../common/page_paths'; +import { SAVED_QUERIES_ID } from './constants'; +import { useErrorToast } from '../common/hooks/use_error_toast'; + +interface UseDeleteSavedQueryProps { + savedQueryId: string; +} + +export const useDeleteSavedQuery = ({ savedQueryId }: UseDeleteSavedQueryProps) => { + const queryClient = useQueryClient(); + const { + application: { navigateToApp }, + savedObjects, + notifications: { toasts }, + } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useMutation(() => savedObjects.client.delete(savedQuerySavedObjectType, savedQueryId), { + onError: (error) => { + // @ts-expect-error update types + setErrorToast(error, { title: error.body.error, toastMessage: error.body.message }); + }, + onSuccess: () => { + queryClient.invalidateQueries(SAVED_QUERIES_ID); + navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); + toasts.addSuccess( + i18n.translate('xpack.osquery.editSavedQuery.deleteSuccessToastMessageText', { + defaultMessage: 'Successfully deleted saved query', + }) + ); + }, + }); +}; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts new file mode 100644 index 0000000000000..324d4aace1647 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from 'react-query'; + +import { useKibana } from '../common/lib/kibana'; +import { savedQuerySavedObjectType } from '../../common/types'; +import { SAVED_QUERIES_ID } from './constants'; + +export const useSavedQueries = ({ + isLive = false, + pageIndex = 0, + pageSize = 10000, + sortField = 'updated_at', + sortDirection = 'desc', +}) => { + const { savedObjects } = useKibana().services; + + return useQuery( + [SAVED_QUERIES_ID, { pageIndex, pageSize, sortField, sortDirection }], + async () => + savedObjects.client.find<{ + id: string; + description?: string; + query: string; + updated_at: string; + updated_by: string; + created_at: string; + created_by: string; + }>({ + type: savedQuerySavedObjectType, + page: pageIndex + 1, + perPage: pageSize, + sortField, + }), + { + keepPreviousData: true, + // Refetch the data every 10 seconds + refetchInterval: isLive ? 10000 : false, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts new file mode 100644 index 0000000000000..92662cd24fd20 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from 'react-query'; + +import { PLUGIN_ID } from '../../common'; +import { useKibana } from '../common/lib/kibana'; +import { savedQuerySavedObjectType } from '../../common/types'; +import { pagePathGetters } from '../common/page_paths'; +import { useErrorToast } from '../common/hooks/use_error_toast'; + +export const SAVED_QUERY_ID = 'savedQuery'; + +interface UseSavedQueryProps { + savedQueryId: string; +} + +export const useSavedQuery = ({ savedQueryId }: UseSavedQueryProps) => { + const { + application: { navigateToApp }, + savedObjects, + } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useQuery( + [SAVED_QUERY_ID, { savedQueryId }], + async () => + savedObjects.client.get<{ + id: string; + description?: string; + query: string; + }>(savedQuerySavedObjectType, savedQueryId), + { + keepPreviousData: true, + onSuccess: (data) => { + if (data.error) { + setErrorToast(data.error, { + title: data.error.error, + toastMessage: data.error.message, + }); + navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); + } + }, + onError: (error) => { + // @ts-expect-error update types + setErrorToast(error, { title: error.body.error, toastMessage: error.body.message }); + }, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_scheduled_query_group.ts b/x-pack/plugins/osquery/public/saved_queries/use_scheduled_query_group.ts new file mode 100644 index 0000000000000..93d552b3f71f3 --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_scheduled_query_group.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useQuery } from 'react-query'; + +import { useKibana } from '../common/lib/kibana'; +import { GetOnePackagePolicyResponse, packagePolicyRouteService } from '../../../fleet/common'; +import { OsqueryManagerPackagePolicy } from '../../common/types'; + +interface UseScheduledQueryGroup { + scheduledQueryGroupId: string; + skip?: boolean; +} + +export const useScheduledQueryGroup = ({ + scheduledQueryGroupId, + skip = false, +}: UseScheduledQueryGroup) => { + const { http } = useKibana().services; + + return useQuery< + Omit & { item: OsqueryManagerPackagePolicy }, + unknown, + OsqueryManagerPackagePolicy + >( + ['scheduledQueryGroup', { scheduledQueryGroupId }], + () => http.get(packagePolicyRouteService.getInfoPath(scheduledQueryGroupId)), + { + keepPreviousData: true, + enabled: !skip, + select: (response) => response.item, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts new file mode 100644 index 0000000000000..1260413676a4e --- /dev/null +++ b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation, useQueryClient } from 'react-query'; +import { i18n } from '@kbn/i18n'; + +import { useKibana } from '../common/lib/kibana'; +import { savedQuerySavedObjectType } from '../../common/types'; +import { PLUGIN_ID } from '../../common'; +import { pagePathGetters } from '../common/page_paths'; +import { SAVED_QUERIES_ID } from './constants'; +import { useErrorToast } from '../common/hooks/use_error_toast'; + +interface UseUpdateSavedQueryProps { + savedQueryId: string; +} + +export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps) => { + const queryClient = useQueryClient(); + const { + application: { navigateToApp }, + savedObjects, + security, + notifications: { toasts }, + } = useKibana().services; + const setErrorToast = useErrorToast(); + + return useMutation( + async (payload) => { + const currentUser = await security.authc.getCurrentUser(); + + if (!currentUser) { + throw new Error('CurrentUser is missing'); + } + + return savedObjects.client.update(savedQuerySavedObjectType, savedQueryId, { + // @ts-expect-error update types + ...payload, + updated_by: currentUser.username, + updated_at: new Date(Date.now()).toISOString(), + }); + }, + { + onError: (error) => { + // @ts-expect-error update types + setErrorToast(error, { title: error.body.error, toastMessage: error.body.message }); + }, + onSuccess: (payload) => { + queryClient.invalidateQueries(SAVED_QUERIES_ID); + navigateToApp(PLUGIN_ID, { path: pagePathGetters.saved_queries() }); + toasts.addSuccess( + i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', { + defaultMessage: 'Successfully updated "{savedQueryName}" query', + values: { + savedQueryName: payload.attributes?.name ?? '', + }, + }) + ); + }, + } + ); +}; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts index 3345c18d07b2c..bedca9d5ef8d7 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/constants.ts @@ -6,6 +6,9 @@ */ export const ALL_OSQUERY_VERSIONS_OPTIONS = [ + { + label: '4.8.0', + }, { label: '4.7.0', }, diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx index 4e433e9e240b1..0d455486bfa25 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/platform_checkbox_group_field.tsx @@ -6,7 +6,7 @@ */ import { isEmpty, pickBy } from 'lodash'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -112,6 +112,15 @@ export const PlatformCheckBoxGroupField = ({ const describedByIds = useMemo(() => (idAria ? [idAria] : []), [idAria]); + useEffect(() => { + setCheckboxIdToSelectedMap(() => + (options as EuiCheckboxGroupOption[]).reduce((acc, option) => { + acc[option.id] = isEmpty(field.value) ? true : field.value?.includes(option.id) ?? false; + return acc; + }, {} as Record) + ); + }, [field.value, options]); + return ( = ({ onSave, onClose, }) => { + const [isEditMode] = useState(!!defaultValue); const { form } = useScheduledQueryGroupQueryForm({ defaultValue, handleSubmit: (payload, isValid) => @@ -67,7 +70,31 @@ const QueryFlyoutComponent: React.FC = ({ [integrationPackageVersion] ); - const { submit } = form; + const { submit, setFieldValue } = form; + + const handleSetQueryValue = useCallback( + (savedQuery) => { + setFieldValue('id', savedQuery.id); + setFieldValue('query', savedQuery.query); + + if (savedQuery.description) { + setFieldValue('description', savedQuery.description); + } + + if (savedQuery.interval) { + setFieldValue('interval', savedQuery.interval); + } + + if (isFieldSupported && savedQuery.platform) { + setFieldValue('platform', savedQuery.platform); + } + + if (isFieldSupported && savedQuery.version) { + setFieldValue('version', [savedQuery.version]); + } + }, + [isFieldSupported, setFieldValue] + ); return ( @@ -75,7 +102,7 @@ const QueryFlyoutComponent: React.FC = ({

    - {defaultValue ? ( + {isEditMode ? ( = ({
    + {!isEditMode ? ( + <> + + + + ) : null} - + Set heading level based on context

    } + description={'Will be wrapped in a small, subdued EuiText block.'} + > = ({ euiFieldProps={{ disabled: !isFieldSupported }} /> -
    + {!isFieldSupported ? ( diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx index 344c33b419dd6..d8dbaad2f17e8 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx @@ -22,6 +22,16 @@ export const formSchema = { }), validations: idFieldValidations.map((validator) => ({ validator })), }, + description: { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.descriptionFieldLabel', + { + defaultMessage: 'Description', + } + ), + validations: [], + }, query: { type: FIELD_TYPES.TEXT, label: i18n.translate('xpack.osquery.scheduledQueryGroup.queryFlyoutForm.queryFieldLabel', { diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx index bcde5f4b970d4..fdf781c6d6f7a 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx @@ -45,6 +45,9 @@ export const useScheduledQueryGroupQueryForm = ({ // @ts-expect-error update types serializer: (payload) => produce(payload, (draft) => { + if (isArray(draft.platform)) { + draft.platform.join(','); + } if (draft.platform?.split(',').length === 3) { // if all platforms are checked then use undefined delete draft.platform; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_groups_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_groups_table.tsx index 7b5f91157132e..391e20c63653f 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_groups_table.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_groups_table.tsx @@ -6,6 +6,7 @@ */ import { EuiInMemoryTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; +import moment from 'moment'; import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; @@ -37,6 +38,13 @@ const ScheduledQueryGroupsTableComponent = () => { const renderActive = useCallback((_, item) => , []); + const renderUpdatedAt = useCallback((updatedAt, item) => { + if (!updatedAt) return '-'; + + const updatedBy = item.updated_by !== item.created_by ? ` @ ${item.updated_by}` : ''; + return updatedAt ? `${moment(updatedAt).fromNow()}${updatedBy}` : '-'; + }, []); + const columns: Array> = useMemo( () => [ { @@ -66,6 +74,21 @@ const ScheduledQueryGroupsTableComponent = () => { render: renderQueries, width: '150px', }, + { + field: 'created_by', + name: i18n.translate('xpack.osquery.scheduledQueryGroups.table.createdByColumnTitle', { + defaultMessage: 'Created by', + }), + sortable: true, + truncateText: true, + }, + { + field: 'updated_at', + name: 'Last updated', + sortable: (item) => (item.updated_at ? Date.parse(item.updated_at) : 0), + truncateText: true, + render: renderUpdatedAt, + }, { field: 'enabled', name: i18n.translate('xpack.osquery.scheduledQueryGroups.table.activeColumnTitle', { @@ -77,7 +100,7 @@ const ScheduledQueryGroupsTableComponent = () => { render: renderActive, }, ], - [renderActive, renderAgentPolicy, renderQueries] + [renderActive, renderAgentPolicy, renderQueries, renderUpdatedAt] ); const sorting = useMemo( diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts index 441c00f2d0968..9a466dfc619b6 100644 --- a/x-pack/plugins/osquery/public/types.ts +++ b/x-pack/plugins/osquery/public/types.ts @@ -8,7 +8,8 @@ import { DiscoverStart } from '../../../../src/plugins/discover/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { FleetStart } from '../../fleet/public'; -import { LensPublicStart } from '../../../plugins/lens/public'; +import { LensPublicStart } from '../../lens/public'; +import { SecurityPluginStart } from '../../security/public'; import { CoreStart } from '../../../../src/core/public'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; import { @@ -30,6 +31,7 @@ export interface StartPlugins { data: DataPublicPluginStart; fleet: FleetStart; lens?: LensPublicStart; + security: SecurityPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; } diff --git a/x-pack/plugins/osquery/scripts/schema_formatter/script.ts b/x-pack/plugins/osquery/scripts/schema_formatter/script.ts index 578c4a1120962..5cdf11d48c696 100644 --- a/x-pack/plugins/osquery/scripts/schema_formatter/script.ts +++ b/x-pack/plugins/osquery/scripts/schema_formatter/script.ts @@ -37,7 +37,7 @@ run( const mapFunc = pullFields.bind(null, { name: true }); const formattedSchema = schemaData.map(mapFunc); await fs.writeFile( - path.join(schemaPath, `${flags.schema_version}-formatted`), + path.join(schemaPath, `${flags.schema_version}-formatted.json`), JSON.stringify(formattedSchema) ); }, diff --git a/x-pack/plugins/osquery/server/config.ts b/x-pack/plugins/osquery/server/config.ts index 56d67400a47d9..3ec9213ae6d60 100644 --- a/x-pack/plugins/osquery/server/config.ts +++ b/x-pack/plugins/osquery/server/config.ts @@ -10,7 +10,7 @@ import { TypeOf, schema } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), actionEnabled: schema.boolean({ defaultValue: false }), - savedQueries: schema.boolean({ defaultValue: false }), + savedQueries: schema.boolean({ defaultValue: true }), packs: schema.boolean({ defaultValue: false }), }); diff --git a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts index 5b1f8e780494d..6ebf469b8fb29 100644 --- a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts +++ b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts @@ -6,6 +6,7 @@ */ import { Logger, LoggerFactory } from 'src/core/server'; +import { SecurityPluginStart } from '../../../security/server'; import { AgentService, FleetStartContract, @@ -69,7 +70,7 @@ export class OsqueryAppContextService { export interface OsqueryAppContext { logFactory: LoggerFactory; config(): ConfigType; - + security: SecurityPluginStart; /** * Object readiness is tied to plugin start method */ diff --git a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts index dadcea6e2fd4d..537b6d7874ab8 100644 --- a/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts +++ b/x-pack/plugins/osquery/server/lib/saved_query/saved_object_mappings.ts @@ -14,34 +14,40 @@ export const savedQuerySavedObjectMappings: SavedObjectsType['mappings'] = { description: { type: 'text', }, - name: { - type: 'text', + id: { + type: 'keyword', }, query: { type: 'text', }, - created: { + created_at: { type: 'date', }, - createdBy: { + created_by: { type: 'text', }, platform: { type: 'keyword', }, - updated: { + version: { + type: 'keyword', + }, + updated_at: { type: 'date', }, - updatedBy: { + updated_by: { type: 'text', }, + interval: { + type: 'keyword', + }, }, }; export const savedQueryType: SavedObjectsType = { name: savedQuerySavedObjectType, hidden: false, - namespaceType: 'single', + namespaceType: 'multiple-isolated', mappings: savedQuerySavedObjectMappings, }; @@ -53,16 +59,16 @@ export const packSavedObjectMappings: SavedObjectsType['mappings'] = { name: { type: 'text', }, - created: { + created_at: { type: 'date', }, - createdBy: { + created_by: { type: 'text', }, - updated: { + updated_at: { type: 'date', }, - updatedBy: { + updated_by: { type: 'text', }, queries: { @@ -81,6 +87,6 @@ export const packSavedObjectMappings: SavedObjectsType['mappings'] = { export const packType: SavedObjectsType = { name: packSavedObjectType, hidden: false, - namespaceType: 'single', + namespaceType: 'multiple-isolated', mappings: packSavedObjectMappings, }; diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts index ae779a9788238..6bc12f5736e5e 100644 --- a/x-pack/plugins/osquery/server/plugin.ts +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -46,6 +46,7 @@ export class OsqueryPlugin implements Plugin config, + security: plugins.security, }; initSavedObjects(core.savedObjects, osqueryContext); diff --git a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts index 86e871f041160..478bfc1053bdf 100644 --- a/x-pack/plugins/osquery/server/routes/action/create_action_route.ts +++ b/x-pack/plugins/osquery/server/routes/action/create_action_route.ts @@ -48,13 +48,15 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon } try { + const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username; const action = { action_id: uuid.v4(), '@timestamp': moment().toISOString(), - expiration: moment().add(1, 'days').toISOString(), + expiration: moment().add(5, 'minutes').toISOString(), type: 'INPUT_ACTION', input_type: 'osquery', agents: selectedAgents, + user_id: currentUser, data: { id: uuid.v4(), query: request.body.query, @@ -75,7 +77,7 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon incrementCount(soClient, 'live_query', 'errors'); return response.customError({ statusCode: 500, - body: new Error(`Error occurred whlie processing ${error}`), + body: new Error(`Error occurred while processing ${error}`), }); } } diff --git a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts index 5eb7147d46607..a41cb7cc39b40 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts @@ -28,13 +28,15 @@ export const createSavedQueryRoute = (router: IRouter) => { async (context, request, response) => { const savedObjectsClient = context.core.savedObjects.client; - const { name, description, platform, query } = request.body; + const { id, description, platform, query, version, interval } = request.body; const savedQuerySO = await savedObjectsClient.create(savedQuerySavedObjectType, { - name, + id, description, query, platform, + version, + interval, }); return response.ok({ diff --git a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts index 8be4c6c50d821..2d399648df4cc 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts @@ -21,18 +21,14 @@ export const readSavedQueryRoute = (router: IRouter) => { async (context, request, response) => { const savedObjectsClient = context.core.savedObjects.client; - const { attributes, ...savedQuery } = await savedObjectsClient.get( + const savedQuery = await savedObjectsClient.get( savedQuerySavedObjectType, // @ts-expect-error update types request.params.id ); return response.ok({ - body: { - ...savedQuery, - // @ts-expect-error update types - ...attributes, - }, + body: savedQuery, }); } ); diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts index 579cd9b654cc0..f9ecf675489dc 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts @@ -23,17 +23,19 @@ export const updateSavedQueryRoute = (router: IRouter) => { const savedObjectsClient = context.core.savedObjects.client; // @ts-expect-error update types - const { name, description, platform, query } = request.body; + const { id, description, platform, query, version, interval } = request.body; const savedQuerySO = await savedObjectsClient.update( savedQuerySavedObjectType, // @ts-expect-error update types request.params.id, { - name, + id, description, platform, query, + version, + interval, } ); diff --git a/x-pack/plugins/osquery/server/types.ts b/x-pack/plugins/osquery/server/types.ts index 667fba2bc98e2..84b2ff41dc1cf 100644 --- a/x-pack/plugins/osquery/server/types.ts +++ b/x-pack/plugins/osquery/server/types.ts @@ -13,6 +13,7 @@ import { import { FleetStartContract } from '../../fleet/server'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { PluginSetupContract } from '../../features/server'; +import { SecurityPluginStart } from '../../security/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface OsqueryPluginSetup {} @@ -24,6 +25,7 @@ export interface SetupPlugins { actions: ActionsPlugin['setup']; data: DataPluginSetup; features: PluginSetupContract; + security: SecurityPluginStart; } export interface StartPlugins { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index ca2be10624e66..7ffd5d8f20ffd 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17336,7 +17336,7 @@ "xpack.osquery.breadcrumbs.newLiveQueryPageTitle": "新規", "xpack.osquery.breadcrumbs.overviewPageTitle": "概要", "xpack.osquery.breadcrumbs.scheduledQueryGroupsPageTitle": "スケジュールされたクエリグループ", - "xpack.osquery.codeEditorField.osquerySchemaLinkLabel": "Osqueryスキーマ", + "xpack.osquery.osquerySchemaLinkLabel": "Osqueryスキーマ", "xpack.osquery.common.tabBetaBadgeLabel": "ベータ", "xpack.osquery.common.tabBetaBadgeTooltipContent": "この機能は現在開発中です。他にも機能が追加され、機能によっては変更されるものもあります。", "xpack.osquery.editScheduledQuery.pageTitle": "{queryName}を編集", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 341741dd4046a..4a964fc5e2fd0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17573,7 +17573,7 @@ "xpack.osquery.breadcrumbs.newLiveQueryPageTitle": "新建", "xpack.osquery.breadcrumbs.overviewPageTitle": "概览", "xpack.osquery.breadcrumbs.scheduledQueryGroupsPageTitle": "已计划查询组", - "xpack.osquery.codeEditorField.osquerySchemaLinkLabel": "Osquery 架构", + "xpack.osquery.osquerySchemaLinkLabel": "Osquery 架构", "xpack.osquery.common.tabBetaBadgeLabel": "公测版", "xpack.osquery.common.tabBetaBadgeTooltipContent": "我们正在开发此功能。将会有更多的功能,某些功能可能有变更。", "xpack.osquery.createScheduledQuery.agentPolicyAgentsCountText": "{count, plural, other {# 个代理}}已注册", From da13795ed4e3c511ce44b87da7a5d146971f3866 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Mon, 28 Jun 2021 22:03:08 -0400 Subject: [PATCH 42/74] Task/host isolation status pending (#103549) --- .../event_details/alert_summary_view.tsx | 4 ++-- .../detection_engine/alerts/translations.ts | 5 +++++ .../alerts/use_host_isolation_status.tsx | 22 +++++++++++++++++-- .../body/renderers/agent_statuses.tsx | 13 +++++++++-- .../body/renderers/formatted_field.tsx | 2 +- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 59acb16c028d8..d89f44542318e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -183,7 +183,7 @@ const AlertSummaryViewComponent: React.FC<{ return endpointAlertCheck({ data }); }, [data]); - const agentId = useMemo(() => { + const endpointId = useMemo(() => { const findAgentId = find({ category: 'agent', field: 'agent.id' }, data)?.values; return findAgentId ? findAgentId[0] : ''; }, [data]); @@ -194,7 +194,7 @@ const AlertSummaryViewComponent: React.FC<{ contextId: timelineId, eventId, fieldName: 'agent.status', - value: agentId, + value: endpointId, linkValue: undefined, }, }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts index 9e4497f2f096b..d5234e719b869 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/translations.ts @@ -42,3 +42,8 @@ export const ISOLATION_STATUS_FAILURE = i18n.translate( 'xpack.securitySolution.endpoint.hostIsolation.isolationStatus.title', { defaultMessage: 'Failed to retrieve current isolation status' } ); + +export const ISOLATION_PENDING_FAILURE = i18n.translate( + 'xpack.securitySolution.endpoint.hostIsolation.isolationPending.title', + { defaultMessage: 'Failed to retrieve isolation pending statuses' } +); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx index 7419727fff6a2..3bdd8c9813785 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx @@ -9,7 +9,8 @@ import { isEmpty } from 'lodash'; import { useEffect, useState } from 'react'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { getHostMetadata } from './api'; -import { ISOLATION_STATUS_FAILURE } from './translations'; +import { ISOLATION_STATUS_FAILURE, ISOLATION_PENDING_FAILURE } from './translations'; +import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; import { isEndpointHostIsolated } from '../../../../common/utils/validators'; import { HostStatus } from '../../../../../common/endpoint/types'; @@ -17,6 +18,8 @@ interface HostIsolationStatusResponse { loading: boolean; isIsolated: boolean; agentStatus: HostStatus; + pendingIsolation: number; + pendingUnisolation: number; } /* @@ -28,6 +31,8 @@ export const useHostIsolationStatus = ({ }): HostIsolationStatusResponse => { const [isIsolated, setIsIsolated] = useState(false); const [agentStatus, setAgentStatus] = useState(HostStatus.UNHEALTHY); + const [pendingIsolation, setPendingIsolation] = useState(0); + const [pendingUnisolation, setPendingUnisolation] = useState(0); const [loading, setLoading] = useState(false); const { addError } = useAppToasts(); @@ -35,16 +40,29 @@ export const useHostIsolationStatus = ({ useEffect(() => { // isMounted tracks if a component is mounted before changing state let isMounted = true; + let fleetAgentId: string; const fetchData = async () => { try { const metadataResponse = await getHostMetadata({ agentId }); if (isMounted) { setIsIsolated(isEndpointHostIsolated(metadataResponse.metadata)); setAgentStatus(metadataResponse.host_status); + fleetAgentId = metadataResponse.metadata.elastic.agent.id; } } catch (error) { addError(error.message, { title: ISOLATION_STATUS_FAILURE }); } + + try { + const { data } = await fetchPendingActionsByAgentId(fleetAgentId); + if (isMounted) { + setPendingIsolation(data[0].pending_actions?.isolate ?? 0); + setPendingUnisolation(data[0].pending_actions?.unisolate ?? 0); + } + } catch (error) { + addError(error.message, { title: ISOLATION_PENDING_FAILURE }); + } + if (isMounted) { setLoading(false); } @@ -64,5 +82,5 @@ export const useHostIsolationStatus = ({ isMounted = false; }; }, [addError, agentId]); - return { loading, isIsolated, agentStatus }; + return { loading, isIsolated, agentStatus, pendingIsolation, pendingUnisolation }; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx index 16f11809dd72b..2c88b305c7d05 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx @@ -24,7 +24,12 @@ export const AgentStatuses = React.memo( eventId: string; value: string; }) => { - const { isIsolated, agentStatus } = useHostIsolationStatus({ agentId: value }); + const { + isIsolated, + agentStatus, + pendingIsolation, + pendingUnisolation, + } = useHostIsolationStatus({ agentId: value }); const isolationFieldName = 'host.isolation'; return ( @@ -45,7 +50,11 @@ export const AgentStatuses = React.memo( tooltipContent={isolationFieldName} value={`${isIsolated}`} > - +
    diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 3d5d410abb87e..1d04849b198ad 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -126,7 +126,7 @@ const FormattedFieldValueComponent: React.FC<{ contextId={contextId} eventId={eventId} fieldName={fieldName} - value={value as string} + value={typeof value === 'string' ? value : ''} /> ); } else if ( From 1dce600efecf22138ddd225c10b06c6464515a45 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 28 Jun 2021 21:27:10 -0500 Subject: [PATCH 43/74] Collect `host.os.platform` telemetry for APM (#103520) Fixes #97958. --- .../__snapshots__/apm_telemetry.test.ts.snap | 22 +++++++++ .../collect_data_telemetry/tasks.test.ts | 38 +++++++++++++++ .../collect_data_telemetry/tasks.ts | 48 +++++++++++++++++++ .../apm/server/lib/apm_telemetry/schema.ts | 2 + .../apm/server/lib/apm_telemetry/types.ts | 2 + .../schema/xpack_plugins.json | 33 +++++++++++-- 6 files changed, 141 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap index d7fc8e6442f12..71b0929164705 100644 --- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap @@ -675,6 +675,17 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the } } }, + "host": { + "properties": { + "os": { + "properties": { + "platform": { + "type": "keyword" + } + } + } + } + }, "counts": { "properties": { "transaction": { @@ -967,6 +978,17 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the } } }, + "host": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, "processor_events": { "properties": { "took": { diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts index 129da71097863..4bfac442b4a3c 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts @@ -209,6 +209,44 @@ describe('data telemetry collection tasks', () => { }); }); + describe('host', () => { + const task = tasks.find((t) => t.name === 'host'); + + it('returns a map of host provider data', async () => { + const search = jest.fn().mockResolvedValueOnce({ + aggregations: { + platform: { + buckets: [ + { doc_count: 1, key: 'linux' }, + { doc_count: 1, key: 'windows' }, + { doc_count: 1, key: 'macos' }, + ], + }, + }, + }); + + expect(await task?.executor({ indices, search } as any)).toEqual({ + host: { + os: { platform: ['linux', 'windows', 'macos'] }, + }, + }); + }); + + describe('with no results', () => { + it('returns an empty map', async () => { + const search = jest.fn().mockResolvedValueOnce({}); + + expect(await task?.executor({ indices, search } as any)).toEqual({ + host: { + os: { + platform: [], + }, + }, + }); + }); + }); + }); + describe('processor_events', () => { const task = tasks.find((t) => t.name === 'processor_events'); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index 3d5b4b754e4aa..fd341565c235b 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -20,6 +20,7 @@ import { CONTAINER_ID, ERROR_GROUP_ID, HOST_NAME, + HOST_OS_PLATFORM, OBSERVER_HOSTNAME, PARENT_ID, POD_NAME, @@ -293,6 +294,53 @@ export const tasks: TelemetryTask[] = [ return { cloud }; }, }, + { + name: 'host', + executor: async ({ indices, search }) => { + function getBucketKeys({ + buckets, + }: { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + }) { + return buckets.map((bucket) => bucket.key as string); + } + + const response = await search({ + index: [ + indices['apm_oss.errorIndices'], + indices['apm_oss.metricsIndices'], + indices['apm_oss.spanIndices'], + indices['apm_oss.transactionIndices'], + ], + body: { + size: 0, + timeout, + aggs: { + platform: { + terms: { + field: HOST_OS_PLATFORM, + }, + }, + }, + }, + }); + + const { aggregations } = response; + + if (!aggregations) { + return { host: { os: { platform: [] } } }; + } + const host = { + os: { + platform: getBucketKeys(aggregations.platform), + }, + }; + return { host }; + }, + }, { name: 'environments', executor: async ({ indices, search }) => { diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts index 0b1bc3d50d4c1..b04f64c6bccff 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts @@ -135,6 +135,7 @@ export const apmSchema: MakeSchemaFrom = { provider: { type: 'array', items: { type: 'keyword' } }, region: { type: 'array', items: { type: 'keyword' } }, }, + host: { os: { platform: { type: 'array', items: { type: 'keyword' } } } }, counts: { transaction: timeframeMapSchema, span: timeframeMapSchema, @@ -185,6 +186,7 @@ export const apmSchema: MakeSchemaFrom = { tasks: { aggregated_transactions: { took: { ms: long } }, cloud: { took: { ms: long } }, + host: { took: { ms: long } }, processor_events: { took: { ms: long } }, agent_configuration: { took: { ms: long } }, services: { took: { ms: long } }, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts index 6dc829425eada..cd4e80ff6bf6b 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts @@ -52,6 +52,7 @@ export interface APMUsage { provider: string[]; region: string[]; }; + host: { os: { platform: string[] } }; counts: { transaction: TimeframeMap; span: TimeframeMap; @@ -132,6 +133,7 @@ export interface APMUsage { tasks: Record< | 'aggregated_transactions' | 'cloud' + | 'host' | 'processor_events' | 'agent_configuration' | 'services' 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 bab4244139df0..5a8b30c5c0f26 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -31,10 +31,10 @@ "__index": { "type": "long" }, - "__swimlane": { + "__pagerduty": { "type": "long" }, - "__pagerduty": { + "__swimlane": { "type": "long" }, "__server-log": { @@ -71,10 +71,10 @@ "__index": { "type": "long" }, - "__swimlane": { + "__pagerduty": { "type": "long" }, - "__pagerduty": { + "__swimlane": { "type": "long" }, "__server-log": { @@ -1260,6 +1260,20 @@ } } }, + "host": { + "properties": { + "os": { + "properties": { + "platform": { + "type": "array", + "items": { + "type": "keyword" + } + } + } + } + } + }, "counts": { "properties": { "transaction": { @@ -1552,6 +1566,17 @@ } } }, + "host": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, "processor_events": { "properties": { "took": { From 09bd6301d63803afb503360bdb6c82ffcc497d08 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 29 Jun 2021 08:57:30 +0200 Subject: [PATCH 44/74] [CCR & Snapshot+Restore] Center align states under tabs (#103237) * fix up CCR centered sates in tabs content * update snapshots list * fix lint errors * Change tab states for all pages in snapshot+restore * Remove unnecessary variables * Seems we dont need the class wrapper * fix broken type * Fix bug in ILM table when filtering it down * center align search box * fix linter errors * fix prettier warnings * revert content var refactor and just focus on ux * add breakword class to paragraph so we avoid text overflowing * fix prettier errors Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../authorization/components/page_error.tsx | 6 +- .../auto_follow_pattern_list.test.js | 2 +- .../follower_indices_list.test.js | 2 +- .../auto_follow_pattern_list.js | 47 +-- .../follower_indices_list.js | 47 +-- .../public/shared_imports.ts | 3 + .../sections/policy_table/policy_table.tsx | 6 +- .../helpers/home.helpers.ts | 1 + .../__jest__/client_integration/home.test.ts | 2 +- .../sections/home/policy_list/policy_list.tsx | 93 ++--- .../home/repository_list/repository_list.tsx | 104 ++--- .../home/restore_list/restore_list.tsx | 84 +++-- .../home/snapshot_list/snapshot_list.tsx | 356 ++++++++++-------- .../snapshot_restore/public/shared_imports.ts | 3 + 14 files changed, 415 insertions(+), 341 deletions(-) diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx index 732aa35b05237..9953a8fedb39b 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/page_error.tsx @@ -43,7 +43,11 @@ export const PageError: React.FunctionComponent = ({ body={ error && ( <> - {cause ? message || errorString :

    {message || errorString}

    } + {cause ? ( + message || errorString + ) : ( +

    {message || errorString}

    + )} {cause && ( <> diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js index 1e7eb9562313d..7f7a61a6f0177 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -38,7 +38,7 @@ describe('', () => { }); test('should show a loading indicator on component', async () => { - expect(exists('autoFollowPatternLoading')).toBe(true); + expect(exists('sectionLoading')).toBe(true); }); }); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index f004617a099d3..b9e47b029e302 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -45,7 +45,7 @@ describe('', () => { }); test('should show a loading indicator on component', async () => { - expect(exists('followerIndexLoading')).toBe(true); + expect(exists('sectionLoading')).toBe(true); }); }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index 1ab4e1a3e003a..3a4273c1ed0e3 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -9,13 +9,12 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiPageContent, EuiButton, EuiEmptyPrompt, EuiText, EuiSpacer } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; -import { extractQueryParams, SectionLoading } from '../../../../shared_imports'; +import { extractQueryParams, PageError, PageLoading } from '../../../../shared_imports'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants'; -import { SectionError, SectionUnauthorized } from '../../../components'; import { AutoFollowPatternTable, DetailPanel } from './components'; const REFRESH_RATE_MS = 30000; @@ -98,7 +97,12 @@ export class AutoFollowPatternList extends PureComponent { renderEmpty() { return ( -
    + } /> -
    + ); } @@ -170,19 +174,22 @@ export class AutoFollowPatternList extends PureComponent { if (!isAuthorized) { return ( - } - > - - + error={{ + error: ( + + ), + }} + /> ); } @@ -194,7 +201,7 @@ export class AutoFollowPatternList extends PureComponent { } ); - return ; + return ; } if (isEmpty) { @@ -203,14 +210,12 @@ export class AutoFollowPatternList extends PureComponent { if (apiStatus === API_STATUS.LOADING) { return ( -
    - - - -
    + + + ); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index a52ba0e613ca9..4b593799f5933 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -9,13 +9,12 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiPageContent, EuiButton, EuiEmptyPrompt, EuiText, EuiSpacer } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; -import { extractQueryParams, SectionLoading } from '../../../../shared_imports'; +import { extractQueryParams, PageLoading, PageError } from '../../../../shared_imports'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; -import { SectionError, SectionUnauthorized } from '../../../components'; import { FollowerIndicesTable, DetailPanel } from './components'; const REFRESH_RATE_MS = 30000; @@ -89,7 +88,12 @@ export class FollowerIndicesList extends PureComponent { renderEmpty() { return ( -
    + } /> -
    + ); } renderLoading() { return ( -
    - - - -
    + + + ); } @@ -171,19 +173,22 @@ export class FollowerIndicesList extends PureComponent { if (!isAuthorized) { return ( - } - > - - + error={{ + error: ( + + ), + }} + /> ); } @@ -195,7 +200,7 @@ export class FollowerIndicesList extends PureComponent { } ); - return ; + return ; } if (isEmpty) { diff --git a/x-pack/plugins/cross_cluster_replication/public/shared_imports.ts b/x-pack/plugins/cross_cluster_replication/public/shared_imports.ts index 55a10749230c7..38838968ad212 100644 --- a/x-pack/plugins/cross_cluster_replication/public/shared_imports.ts +++ b/x-pack/plugins/cross_cluster_replication/public/shared_imports.ts @@ -10,4 +10,7 @@ export { indices, SectionLoading, PageError, + PageLoading, } from '../../../../src/plugins/es_ui_shared/public'; + +export { APP_WRAPPER_CLASS } from '../../../../src/core/public'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx index ba89d6c895d93..6ed7d42a694cc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -85,8 +85,10 @@ export const PolicyTable: React.FunctionComponent = ({ ); } + // Wrapping the actual contents inside a div, otherwise the table layout will get messed up + // if the table size changes (ie: applying a filter) due to the flex props of the page wrapper. content = ( - +
    = ({ {tableContent} - +
    ); } else { return ( diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 8a790342134cc..00cec284f3747 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -255,6 +255,7 @@ export type TestSubjects = | 'snapshotDetail.version' | 'snapshotLink' | 'snapshotList' + | 'snapshotListEmpty' | 'snapshotList.cell' | 'snapshotList.closeButton' | 'snapshotList.content' diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index c48d6d78d08f6..9f334ce4d49c8 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -121,7 +121,7 @@ describe('', () => { }); expect(exists('repositoryList')).toBe(false); - expect(exists('snapshotList')).toBe(true); + expect(exists('snapshotListEmpty')).toBe(true); }); }); }); diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index ca1f28c586522..63aae47e63837 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -8,12 +8,13 @@ import React, { Fragment, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { RouteComponentProps } from 'react-router-dom'; -import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { EuiPageContent, EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { - SectionError, + PageLoading, + PageError, Error, WithPrivileges, NotAuthorizedSection, @@ -21,7 +22,6 @@ import { import { SlmPolicy } from '../../../../../common/types'; import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; -import { SectionLoading } from '../../../components'; import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants'; import { useDecodedParams } from '../../../lib'; import { useLoadPolicies, useLoadRetentionSettings } from '../../../services/http'; @@ -89,16 +89,16 @@ export const PolicyList: React.FunctionComponent + - + ); } else if (error) { content = ( - - -

    3x+`w5k8nEV*i{{V~9gFV< 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%0D) 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ÿKY$~R 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 literal 0 HcmV?d00001 diff --git a/docs/user/alerting/images/email-connector-test.png b/docs/user/alerting/images/email-connector-test.png new file mode 100644 index 0000000000000000000000000000000000000000..698b141fda284e0e632d3bdd248701c9c8614f1d GIT binary patch literal 150075 zcmeFZWmp{D(k={y1PCEOkOU`42%g|Nkl;GFOK=7l2=103!5xAp!QEXW1oy#VAi)_N z2A9)$_Osu!ukYjGoFC`sp6i0vy_kbf^Dy0}GC=ZMwVq!`%Vq(-v4tC}c8#5FX>5v3X3@z1e;tX9C8B2dmX{?rz z5iDx@0OD7oh15(>vFPHT5WEYeNKk*fMH6Z!p(&x~^NK^QsRHI^*`(Yb? zinjm#`wMEk|5fJx3X`bXCH-TfiQ~m<^}|4#Kok@@ir?~|Uq|EG6QHwyxe(ErdgE+nFvkAD7S*~ znq&hXF`@|E!^61$!|Umf5Io%8!OZM$j?^xGM;sqqMf(OWLOIW=E|TA0c=;SrYpjm3 zFDZq*PVCG)@=aXvmGjAPXb+~a;i?egFh~$7{)L>CQD%~VUoV$JLQqu5{bPsjweuXR zXo0brY3wv5H_`L@avwjeilS&L(z_LmKYoogf3%DMamqa!c>ko7*r_nvFDXB{Nhups zZs=`k5;!!9qFvj8O^UNFk$va5sM1b(<(WO##Y6`U9OW88V{d%_hZ@QYtIu@&_l%(* za0nZ)lǏ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

    - } - body={ - -

    + + -

    -
    - } - actions={ - - - - } - data-test-subj="emptyPrompt" - /> + + } + body={ + +

    + +

    +
    + } + actions={ + + + + } + data-test-subj="emptyPrompt" + /> + ); } else { const policySchedules = policies.map((policy: SlmPolicy) => policy.schedule); @@ -152,7 +159,7 @@ export const PolicyList: React.FunctionComponent policy.retention)); content = ( - +
    {hasDuplicateSchedules ? ( - +
    ); } @@ -198,7 +205,7 @@ export const PolicyList: React.FunctionComponent `cluster.${name}`)}> {({ hasPrivileges, privilegesMissing }) => hasPrivileges ? ( -
    + <> {policyName ? ( ) : null} {content} -
    + ) : ( + - + ); } else if (error) { content = ( - - - - } - body={ - -

    + + -

    -
    - } - actions={ - - - - } - data-test-subj="emptyPrompt" - /> + + } + body={ + +

    + +

    +
    + } + actions={ + + + + } + data-test-subj="emptyPrompt" + /> + ); } else { content = ( - +
    + +
    ); } return ( -
    + <> {repositoryName ? ( ) : null} {content} -
    + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx index 8dabaf4f29847..14044d3aaa161 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx @@ -8,6 +8,7 @@ import React, { useEffect, useState, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { + EuiPageContent, EuiEmptyPrompt, EuiPopover, EuiButtonEmpty, @@ -23,10 +24,10 @@ import { APP_RESTORE_INDEX_PRIVILEGES } from '../../../../../common'; import { WithPrivileges, NotAuthorizedSection, - SectionError, + PageError, + PageLoading, Error, } from '../../../../shared_imports'; -import { SectionLoading } from '../../../components'; import { UIM_RESTORE_LIST_LOAD } from '../../../constants'; import { useLoadRestores } from '../../../services/http'; import { linkToSnapshots } from '../../../services/navigation'; @@ -74,18 +75,18 @@ export const RestoreList: React.FunctionComponent = () => { if (isLoading) { // Because we're polling for new data, we only want to hide the list during the initial fetch. content = ( - + - + ); } else if (error) { // If we get an error while polling we don't need to show it to the user because they can still // work with the table. content = ( - { } else { if (restores && restores.length === 0) { content = ( - - - - } - body={ - -

    + + - - - ), - }} + id="xpack.snapshotRestore.restoreList.emptyPromptTitle" + defaultMessage="No restored snapshots" /> -

    -
    - } - data-test-subj="emptyPrompt" - /> + + } + body={ + +

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

    +
    + } + data-test-subj="emptyPrompt" + /> + ); } else { content = ( - +
    { - +
    ); } } @@ -217,7 +225,7 @@ export const RestoreList: React.FunctionComponent = () => { `index.${name}`)}> {({ hasPrivileges, privilegesMissing }) => hasPrivileges ? ( -
    {content}
    + content ) : ( - - + + + + + ); } else if (error) { content = ( - - - - } - body={ -

    - - - - ), - }} - /> -

    - } - /> + + + + + } + body={ +

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

    + } + /> +
    ); } else if (repositories.length === 0) { content = ( - - - - } - body={ - <> -

    + + -

    -

    - + + } + body={ + <> +

    - -

    - - } - data-test-subj="emptyPrompt" - /> +

    +

    + + + +

    + + } + data-test-subj="emptyPrompt" + /> + ); } else if (snapshots.length === 0) { content = ( - - - - } - body={ - `cluster.${name}`)}> - {({ hasPrivileges }) => - hasPrivileges ? ( - -

    - - - - ), - }} - /> -

    -

    - {policies.length === 0 ? ( - - - - ) : ( - + + + + } + body={ + `cluster.${name}`)} + > + {({ hasPrivileges }) => + hasPrivileges ? ( + +

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

    +

    + {policies.length === 0 ? ( + + + + ) : ( + + + + )} +

    +
    + ) : ( + +

    + +

    +

    + - - )} -

    -
    - ) : ( - -

    - -

    -

    - - {' '} - - -

    -
    - ) - } -
    - } - data-test-subj="emptyPrompt" - /> + id="xpack.snapshotRestore.emptyPrompt.noSnapshotsDocLinkText" + defaultMessage="Learn how to create a snapshot" + />{' '} + + +

    +
    + ) + } + + } + data-test-subj="emptyPrompt" + /> + ); } else { const repositoryErrorsWarning = Object.keys(errors).length ? ( @@ -322,7 +350,7 @@ export const SnapshotList: React.FunctionComponent +
    {repositoryErrorsWarning} - +
    ); } return ( -
    + <> {repositoryName && snapshotId ? ( ) : null} {content} -
    + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts index 759453edaba5d..84c195a51950b 100644 --- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts +++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts @@ -13,6 +13,7 @@ export { NotAuthorizedSection, SectionError, PageError, + PageLoading, sendRequest, SendRequestConfig, SendRequestResponse, @@ -22,3 +23,5 @@ export { UseRequestConfig, WithPrivileges, } from '../../../../src/plugins/es_ui_shared/public'; + +export { APP_WRAPPER_CLASS } from '../../../../src/core/public'; From ac17ab1436986b37f9d0a3969aff70e728481cf7 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 29 Jun 2021 08:58:37 +0200 Subject: [PATCH 45/74] Add signal and abort controller to agent metadata and TakeAction button (#103217) --- .../containers/detection_engine/alerts/api.ts | 10 ++++++++-- .../alerts/use_host_isolation_status.tsx | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 05706981a681d..72a9bf6e84441 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -178,8 +178,14 @@ export const getCaseIdsFromAlertId = async ({ * * @param host id */ -export const getHostMetadata = async ({ agentId }: { agentId: string }): Promise => +export const getHostMetadata = async ({ + agentId, + signal, +}: { + agentId: string; + signal?: AbortSignal; +}): Promise => KibanaServices.get().http.fetch( resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: agentId }), - { method: 'get' } + { method: 'GET', signal } ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx index 3bdd8c9813785..259a377b10b79 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx @@ -38,18 +38,23 @@ export const useHostIsolationStatus = ({ const { addError } = useAppToasts(); useEffect(() => { + const abortCtrl = new AbortController(); // isMounted tracks if a component is mounted before changing state let isMounted = true; let fleetAgentId: string; const fetchData = async () => { try { - const metadataResponse = await getHostMetadata({ agentId }); + const metadataResponse = await getHostMetadata({ agentId, signal: abortCtrl.signal }); if (isMounted) { setIsIsolated(isEndpointHostIsolated(metadataResponse.metadata)); setAgentStatus(metadataResponse.host_status); fleetAgentId = metadataResponse.metadata.elastic.agent.id; } } catch (error) { + // don't show self-aborted requests errors to the user + if (error.name === 'AbortError') { + return; + } addError(error.message, { title: ISOLATION_STATUS_FAILURE }); } @@ -80,6 +85,7 @@ export const useHostIsolationStatus = ({ return () => { // updates to show component is unmounted isMounted = false; + abortCtrl.abort(); }; }, [addError, agentId]); return { loading, isIsolated, agentStatus, pendingIsolation, pendingUnisolation }; From 4f63abc1e51710bb1ef8f3ba11b1059af51bbba4 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 29 Jun 2021 10:19:50 +0200 Subject: [PATCH 46/74] [Lens] Do not persist time zone (#102735) --- .../indexpattern_datasource/loader.test.ts | 2 +- .../definitions/date_histogram.test.tsx | 135 +----------------- .../operations/definitions/date_histogram.tsx | 48 ++----- .../operations/layer_helpers.test.ts | 11 +- .../embeddable/lens_embeddable_factory.ts | 15 +- .../server/migrations/common_migrations.ts | 36 ++++- .../saved_object_migrations.test.ts | 92 ++++++++++++ .../migrations/saved_object_migrations.ts | 18 ++- .../plugins/lens/server/migrations/types.ts | 65 +++++++++ 9 files changed, 248 insertions(+), 174 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 192f3d3c0c535..d58d9e593e101 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -770,7 +770,7 @@ describe('loader', () => { label: 'My hist', operationType: 'date_histogram', params: { - interval: '1d', + interval: 'm', }, sourceField: 'timestamp', }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index 28325c8d2b601..3b7554d8110f8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -176,30 +176,6 @@ describe('date_histogram', () => { }); expect(column.params.interval).toEqual('auto'); }); - - it('should create column object with restrictions', () => { - const column = dateHistogramOperation.buildColumn({ - layer: { columns: {}, columnOrder: [], indexPatternId: '' }, - indexPattern: createMockedIndexPattern(), - field: { - name: 'timestamp', - displayName: 'timestampLabel', - type: 'date', - esTypes: ['date'], - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - time_zone: 'UTC', - calendar_interval: '1y', - }, - }, - }, - }); - expect(column.params.interval).toEqual('1y'); - expect(column.params.timeZone).toEqual('UTC'); - }); }); describe('toEsAggsFn', () => { @@ -223,7 +199,7 @@ describe('date_histogram', () => { ); }); - it('should not use normalized es interval for rollups', () => { + it('should use restricted time zone and omit use normalized es interval for rollups', () => { const esAggsFn = dateHistogramOperation.toEsAggsFn( layer.columns.col1 as DateHistogramIndexPatternColumn, 'col1', @@ -271,6 +247,7 @@ describe('date_histogram', () => { arguments: expect.objectContaining({ interval: ['42w'], field: ['timestamp'], + time_zone: ['UTC'], useNormalizedEsInterval: [false], }), }) @@ -320,114 +297,6 @@ describe('date_histogram', () => { }); }); - describe('transfer', () => { - it('should adjust interval and time zone params if that is necessary due to restrictions', () => { - const transferedColumn = dateHistogramOperation.transfer!( - { - dataType: 'date', - isBucketed: true, - label: '', - operationType: 'date_histogram', - sourceField: 'dateField', - params: { - interval: 'd', - }, - }, - { - title: '', - id: '', - hasRestrictions: true, - fields: [ - { - name: 'dateField', - displayName: 'dateField', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - time_zone: 'CET', - calendar_interval: 'w', - }, - }, - }, - ], - getFieldByName: getFieldByNameFactory([ - { - name: 'dateField', - displayName: 'dateField', - type: 'date', - aggregatable: true, - searchable: true, - aggregationRestrictions: { - date_histogram: { - agg: 'date_histogram', - time_zone: 'CET', - calendar_interval: 'w', - }, - }, - }, - ]), - } - ); - expect(transferedColumn).toEqual( - expect.objectContaining({ - params: { - interval: 'w', - timeZone: 'CET', - }, - }) - ); - }); - - it('should retain interval', () => { - const transferedColumn = dateHistogramOperation.transfer!( - { - dataType: 'date', - isBucketed: true, - label: '', - operationType: 'date_histogram', - sourceField: 'dateField', - params: { - interval: '20s', - }, - }, - { - title: '', - id: '', - hasRestrictions: false, - fields: [ - { - name: 'dateField', - displayName: 'dateField', - type: 'date', - aggregatable: true, - searchable: true, - }, - ], - getFieldByName: getFieldByNameFactory([ - { - name: 'dateField', - displayName: 'dateField', - type: 'date', - aggregatable: true, - searchable: true, - }, - ]), - } - ); - expect(transferedColumn).toEqual( - expect.objectContaining({ - params: { - interval: '20s', - timeZone: undefined, - }, - }) - ); - }); - }); - describe('param editor', () => { it('should render current value', () => { const updateLayerSpy = jest.fn(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index a645079c7b9f8..0091c43601202 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -45,7 +45,6 @@ export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternC operationType: 'date_histogram'; params: { interval: string; - timeZone?: string; }; } @@ -106,12 +105,6 @@ export const dateHistogramOperation: OperationDefinition< }, getDefaultLabel: (column, indexPattern) => getSafeName(column.sourceField, indexPattern), buildColumn({ field }, columnParams) { - let interval = columnParams?.interval ?? autoInterval; - let timeZone: string | undefined; - if (field.aggregationRestrictions && field.aggregationRestrictions.date_histogram) { - interval = restrictedInterval(field.aggregationRestrictions) as string; - timeZone = field.aggregationRestrictions.date_histogram.time_zone; - } return { label: field.displayName, dataType: 'date', @@ -120,8 +113,7 @@ export const dateHistogramOperation: OperationDefinition< isBucketed: true, scale: 'interval', params: { - interval, - timeZone, + interval: columnParams?.interval ?? autoInterval, }, }; }, @@ -135,28 +127,6 @@ export const dateHistogramOperation: OperationDefinition< (!newField.aggregationRestrictions || newField.aggregationRestrictions.date_histogram) ); }, - transfer: (column, newIndexPattern) => { - const newField = newIndexPattern.getFieldByName(column.sourceField); - - if (newField?.aggregationRestrictions?.date_histogram) { - const restrictions = newField.aggregationRestrictions.date_histogram; - - return { - ...column, - params: { - ...column.params, - timeZone: restrictions.time_zone, - // TODO this rewrite logic is simplified - if the current interval is a multiple of - // the restricted interval, we could carry it over directly. However as the current - // UI does not allow to select multiples of an interval anyway, this is not included yet. - // If the UI allows to pick more complicated intervals, this should be re-visited. - interval: restrictedInterval(newField.aggregationRestrictions) as string, - }, - }; - } - - return column; - }, onFieldChange: (oldColumn, field) => { return { ...oldColumn, @@ -166,14 +136,24 @@ export const dateHistogramOperation: OperationDefinition< }, toEsAggsFn: (column, columnId, indexPattern) => { const usedField = indexPattern.getFieldByName(column.sourceField); + let timeZone: string | undefined; + let interval = column.params?.interval ?? autoInterval; + if ( + usedField && + usedField.aggregationRestrictions && + usedField.aggregationRestrictions.date_histogram + ) { + interval = restrictedInterval(usedField.aggregationRestrictions) as string; + timeZone = usedField.aggregationRestrictions.date_histogram.time_zone; + } return buildExpressionFunction('aggDateHistogram', { id: columnId, enabled: true, schema: 'segment', field: column.sourceField, - time_zone: column.params.timeZone, + time_zone: timeZone, useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram, - interval: column.params.interval, + interval, drop_partials: false, min_doc_count: 0, extended_bounds: JSON.stringify({}), @@ -244,7 +224,7 @@ export const dateHistogramOperation: OperationDefinition< id="xpack.lens.indexPattern.dateHistogram.restrictedInterval" defaultMessage="Interval fixed to {intervalValue} due to aggregation restrictions." values={{ - intervalValue: currentColumn.params.interval, + intervalValue: restrictedInterval(field!.aggregationRestrictions), }} /> ) : ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 9eedae6d82d43..ef1de43ad06ed 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -25,6 +25,7 @@ import { documentField } from '../document_field'; import { getFieldByNameFactory } from '../pure_helpers'; import { generateId } from '../../id_generator'; import { createMockedFullReference, createMockedManagedReference } from './mocks'; +import { IndexPatternColumn, OperationDefinition } from './definitions'; import { TinymathAST } from 'packages/kbn-tinymath'; jest.mock('../operations'); @@ -2642,7 +2643,15 @@ describe('state_helpers', () => { }); }); + // no operation currently requires this check, faking a transfer function to check whether the generic logic works it('should rewrite column params if that is necessary due to restrictions', () => { + operationDefinitionMap.date_histogram.transfer = ((oldColumn) => ({ + ...oldColumn, + params: { + ...oldColumn.params, + interval: 'w', + }, + })) as OperationDefinition['transfer']; const layer: IndexPatternLayer = { columnOrder: ['col1', 'col2'], columns: { @@ -2666,10 +2675,10 @@ describe('state_helpers', () => { ...layer.columns.col1, params: { interval: 'w', - timeZone: 'CET', }, }, }); + delete operationDefinitionMap.date_histogram.transfer; }); it('should remove operations referencing fields with wrong field types', () => { diff --git a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts index ddc822f37b95b..4f21378cc8115 100644 --- a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts @@ -8,8 +8,11 @@ import { EmbeddableRegistryDefinition } from 'src/plugins/embeddable/server'; import { SerializableState } from '../../../../../src/plugins/kibana_utils/common'; import { DOC_TYPE } from '../../common'; -import { commonRenameOperationsForFormula } from '../migrations/common_migrations'; -import { LensDocShapePre712 } from '../migrations/types'; +import { + commonRemoveTimezoneDateHistogramParam, + commonRenameOperationsForFormula, +} from '../migrations/common_migrations'; +import { LensDocShape713, LensDocShapePre712 } from '../migrations/types'; export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { return { @@ -24,6 +27,14 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { attributes: migratedLensState, } as unknown) as SerializableState; }, + '7.14.0': (state) => { + const lensState = (state as unknown) as { attributes: LensDocShape713 }; + const migratedLensState = commonRemoveTimezoneDateHistogramParam(lensState.attributes); + return ({ + ...lensState, + attributes: migratedLensState, + } as unknown) as SerializableState; + }, }, }; }; diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index 85055e471bac9..db19de7fd9c07 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -6,7 +6,13 @@ */ import { cloneDeep } from 'lodash'; -import { LensDocShapePre712, OperationTypePre712, LensDocShapePost712 } from './types'; +import { + LensDocShapePre712, + OperationTypePre712, + LensDocShapePost712, + LensDocShape713, + LensDocShape714, +} from './types'; export const commonRenameOperationsForFormula = ( attributes: LensDocShapePre712 @@ -44,3 +50,31 @@ export const commonRenameOperationsForFormula = ( ); return newAttributes as LensDocShapePost712; }; + +export const commonRemoveTimezoneDateHistogramParam = ( + attributes: LensDocShape713 +): LensDocShape714 => { + const newAttributes = cloneDeep(attributes); + const datasourceLayers = newAttributes.state.datasourceStates.indexpattern.layers || {}; + (newAttributes as LensDocShapePost712).state.datasourceStates.indexpattern.layers = Object.fromEntries( + Object.entries(datasourceLayers).map(([layerId, layer]) => { + return [ + layerId, + { + ...layer, + columns: Object.fromEntries( + Object.entries(layer.columns).map(([columnId, column]) => { + if (column.operationType === 'date_histogram' && 'params' in column) { + const copy = { ...column, params: { ...column.params } }; + delete copy.params.timeZone; + return [columnId, copy]; + } + return [columnId, column]; + }) + ), + }, + ]; + }) + ); + return newAttributes as LensDocShapePost712; +}; diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index 5478d86e9b14c..9daae1d184ab6 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -852,4 +852,96 @@ describe('Lens migrations', () => { validate(result2); }); }); + + describe('7.14.0 remove time zone from date histogram', () => { + const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const example = { + type: 'lens', + id: 'mocked-saved-object-id', + attributes: { + savedObjectId: '1', + title: 'MyRenamedOps', + description: '', + visualizationType: 'lnsXY', + state: { + datasourceStates: { + indexpattern: { + layers: { + '2': { + columns: { + '3': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto', timeZone: 'Europe/Berlin' }, + }, + '4': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { interval: 'auto' }, + }, + '5': { + label: '@timestamp', + dataType: 'date', + operationType: 'my_unexpected_operation', + isBucketed: true, + scale: 'interval', + params: { timeZone: 'do not delete' }, + }, + }, + columnOrder: ['3', '4', '5'], + incompleteColumns: {}, + }, + }, + }, + }, + visualization: { + title: 'Empty XY chart', + legend: { isVisible: true, position: 'right' }, + valueLabels: 'hide', + preferredSeriesType: 'bar_stacked', + layers: [ + { + layerId: '5ab74ddc-93ca-44e2-9857-ecf85c86b53e', + accessors: [ + '5fea2a56-7b73-44b5-9a50-7f0c0c4f8fd0', + 'e5efca70-edb5-4d6d-a30a-79384066987e', + '7ffb7bde-4f42-47ab-b74d-1b4fd8393e0f', + ], + position: 'top', + seriesType: 'bar_stacked', + showGridlines: false, + xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8', + }, + ], + }, + query: { query: '', language: 'kuery' }, + filters: [], + }, + }, + }; + + it('should remove time zone param from date histogram', () => { + const result = migrations['7.14.0'](example, context) as ReturnType< + SavedObjectMigrationFn + >; + const layers = Object.values(result.attributes.state.datasourceStates.indexpattern.layers); + expect(layers.length).toBe(1); + const columns = Object.values(layers[0].columns); + expect(columns.length).toBe(3); + expect(columns[0].operationType).toEqual('date_histogram'); + expect((columns[0] as { params: {} }).params).toEqual({ interval: 'auto' }); + expect(columns[1].operationType).toEqual('date_histogram'); + expect((columns[1] as { params: {} }).params).toEqual({ interval: 'auto' }); + expect(columns[2].operationType).toEqual('my_unexpected_operation'); + expect((columns[2] as { params: {} }).params).toEqual({ timeZone: 'do not delete' }); + }); + }); }); diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts index ba7004ba67a95..efcd6e2e6f342 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts @@ -15,8 +15,11 @@ import { } from 'src/core/server'; import { Query, Filter } from 'src/plugins/data/public'; import { PersistableFilter } from '../../common'; -import { LensDocShapePost712, LensDocShapePre712 } from './types'; -import { commonRenameOperationsForFormula } from './common_migrations'; +import { LensDocShapePost712, LensDocShapePre712, LensDocShape713, LensDocShape714 } from './types'; +import { + commonRenameOperationsForFormula, + commonRemoveTimezoneDateHistogramParam, +} from './common_migrations'; interface LensDocShapePre710 { visualizationType: string | null; @@ -400,6 +403,16 @@ const renameOperationsForFormula: SavedObjectMigrationFn< }; }; +const removeTimezoneDateHistogramParam: SavedObjectMigrationFn = ( + doc +) => { + const newDoc = cloneDeep(doc); + return { + ...newDoc, + attributes: commonRemoveTimezoneDateHistogramParam(newDoc.attributes), + }; +}; + export const migrations: SavedObjectMigrationMap = { '7.7.0': removeInvalidAccessors, // The order of these migrations matter, since the timefield migration relies on the aggConfigs @@ -410,4 +423,5 @@ export const migrations: SavedObjectMigrationMap = { '7.12.0': transformTableState, '7.13.0': renameOperationsForFormula, '7.13.1': renameOperationsForFormula, // duplicate this migration in case a broken by value panel is added to the library + '7.14.0': removeTimezoneDateHistogramParam, }; diff --git a/x-pack/plugins/lens/server/migrations/types.ts b/x-pack/plugins/lens/server/migrations/types.ts index 38e079ff38051..035e1a86b86f8 100644 --- a/x-pack/plugins/lens/server/migrations/types.ts +++ b/x-pack/plugins/lens/server/migrations/types.ts @@ -87,3 +87,68 @@ export interface LensDocShapePost712 { filters: Filter[]; }; } + +export type LensDocShape713 = Omit & { + state: Omit & { + datasourceStates: { + indexpattern: Omit< + LensDocShapePost712['state']['datasourceStates']['indexpattern'], + 'layers' + > & { + layers: Record< + string, + Omit< + LensDocShapePost712['state']['datasourceStates']['indexpattern']['layers'][string], + 'columns' + > & { + columns: Record< + string, + | { + operationType: OperationTypePost712; + } + | { + operationType: 'date_histogram'; + params: { + interval: string; + timeZone?: string; + }; + } + >; + } + >; + }; + }; + }; +}; + +export type LensDocShape714 = Omit & { + state: Omit & { + datasourceStates: { + indexpattern: Omit< + LensDocShapePost712['state']['datasourceStates']['indexpattern'], + 'layers' + > & { + layers: Record< + string, + Omit< + LensDocShapePost712['state']['datasourceStates']['indexpattern']['layers'][string], + 'columns' + > & { + columns: Record< + string, + | { + operationType: OperationTypePost712; + } + | { + operationType: 'date_histogram'; + params: { + interval: string; + }; + } + >; + } + >; + }; + }; + }; +}; From ce2fabe42084e861af3e9ebeae324be1fcf8ac5b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 29 Jun 2021 10:31:41 +0200 Subject: [PATCH 47/74] add formula telemetry (#103451) --- .../definitions/formula/editor/formula_editor.tsx | 7 ++++++- x-pack/plugins/lens/server/usage/schema.ts | 4 ++++ .../telemetry_collection_xpack/schema/xpack_plugins.json | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx index 83a782b519248..d3341ff119574 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx @@ -745,7 +745,12 @@ export function FormulaEditor({ button={ setIsHelpOpen(!isHelpOpen)} + onClick={() => { + if (!isHelpOpen) { + trackUiEvent('open_formula_popover'); + } + setIsHelpOpen(!isHelpOpen); + }} iconType="documentation" color="text" size="s" diff --git a/x-pack/plugins/lens/server/usage/schema.ts b/x-pack/plugins/lens/server/usage/schema.ts index c3608176717c5..cd4c80360a9b6 100644 --- a/x-pack/plugins/lens/server/usage/schema.ts +++ b/x-pack/plugins/lens/server/usage/schema.ts @@ -14,6 +14,10 @@ const eventsSchema: MakeSchemaFrom = { type: 'long', _meta: { description: 'Number of times the user opened one of the in-product help popovers.' }, }, + open_formula_popover: { + type: 'long', + _meta: { description: 'Number of times the user opened the in-product formula help popover.' }, + }, toggle_fullscreen_formula: { type: 'long', _meta: { 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 5a8b30c5c0f26..89925786636da 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -2190,6 +2190,10 @@ "description": "Number of times the user opened one of the in-product help popovers." } }, + "open_formula_popover": { + "type": "long", + "_meta": { "description": "Number of times the user opened the in-product formula help popover." } + }, "toggle_fullscreen_formula": { "type": "long", "_meta": { @@ -2425,6 +2429,10 @@ "description": "Number of times the user opened one of the in-product help popovers." } }, + "open_formula_popover": { + "type": "long", + "_meta": { "description": "Number of times the user opened the in-product formula help popover." } + }, "toggle_fullscreen_formula": { "type": "long", "_meta": { From 5c87807da1c472c4398ac2923550a3594e943082 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Tue, 29 Jun 2021 10:47:45 +0200 Subject: [PATCH 48/74] [Lens] Remove reference tooltip in Formula panel when the popup is open (#103283) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../formula/editor/formula_editor.tsx | 17 +++++++++++------ .../shared_components/tooltip_wrapper.tsx | 9 +++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx index d3341ff119574..c83135536343d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx @@ -24,7 +24,7 @@ import { monaco } from '@kbn/monaco'; import classNames from 'classnames'; import { CodeEditor } from '../../../../../../../../../src/plugins/kibana_react/public'; import type { CodeEditorProps } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { useDebounceWithOptions } from '../../../../../shared_components'; +import { TooltipWrapper, useDebounceWithOptions } from '../../../../../shared_components'; import { ParamEditorProps } from '../../index'; import { getManagedColumnsFrom } from '../../../layer_helpers'; import { ErrorWrapper, runASTValidation, tryToParse } from '../validation'; @@ -729,11 +729,16 @@ export function FormulaEditor({ ) : ( - - + )} diff --git a/x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx b/x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx index f54cb6bd49dba..0b361c8fa7f1e 100644 --- a/x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx +++ b/x-pack/plugins/lens/public/shared_components/tooltip_wrapper.tsx @@ -6,22 +6,23 @@ */ import React from 'react'; -import { EuiToolTip } from '@elastic/eui'; +import { EuiToolTip, EuiToolTipProps } from '@elastic/eui'; -export interface TooltipWrapperProps { +export type TooltipWrapperProps = Partial> & { tooltipContent: string; condition: boolean; -} +}; export const TooltipWrapper: React.FunctionComponent = ({ children, condition, tooltipContent, + ...tooltipProps }) => { return ( <> {condition ? ( - + <>{children} ) : ( From 409a0f21cc4e9abac6ce28a1ae1b06196899aed9 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 29 Jun 2021 11:21:07 +0200 Subject: [PATCH 49/74] [Exploratory view] use percentages in distribution chart (#103080) --- x-pack/plugins/lens/public/index.ts | 3 + .../operations/definitions/index.ts | 1 + .../operations/index.ts | 2 + .../public/indexpattern_datasource/types.ts | 3 + .../configurations/constants/constants.ts | 1 + .../configurations/lens_attributes.test.ts | 209 +++++++++++++++++- .../configurations/lens_attributes.ts | 36 ++- .../lens_columns/overall_column.tsx | 110 +++++++++ .../rum/data_distribution_config.ts | 6 +- .../synthetics/data_distribution_config.ts | 4 +- .../test_data/sample_attribute.ts | 94 +++++++- .../shared/exploratory_view/types.ts | 1 + 12 files changed, 447 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index a82bc0b9c0d3d..3054f3787a24c 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -54,6 +54,9 @@ export type { CounterRateIndexPatternColumn, DerivativeIndexPatternColumn, MovingAverageIndexPatternColumn, + FormulaIndexPatternColumn, + MathIndexPatternColumn, + OverallSumIndexPatternColumn, } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './editor_frame_service/embeddable'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 246959913c39e..897777a05efc6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -118,6 +118,7 @@ export { export { CountIndexPatternColumn } from './count'; export { LastValueIndexPatternColumn } from './last_value'; export { RangeIndexPatternColumn } from './ranges'; +export { FormulaIndexPatternColumn, MathIndexPatternColumn } from './formula'; // List of all operation definitions registered to this data source. // If you want to implement a new operation, add the definition to this array and diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts index d55c5d3c00f17..7899ce9efcedf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts @@ -35,4 +35,6 @@ export { OverallMinIndexPatternColumn, OverallMaxIndexPatternColumn, OverallAverageIndexPatternColumn, + FormulaIndexPatternColumn, + MathIndexPatternColumn, } from './definitions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index f24c39f810b21..1a3451bdb403b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -32,6 +32,9 @@ export { CounterRateIndexPatternColumn, DerivativeIndexPatternColumn, MovingAverageIndexPatternColumn, + FormulaIndexPatternColumn, + MathIndexPatternColumn, + OverallSumIndexPatternColumn, } from './operations'; export type DraggedField = DragDropIdentifier & { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index 52faa2dccaeac..dd48cf3f7eeb8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -49,6 +49,7 @@ import { export const DEFAULT_TIME = { from: 'now-1h', to: 'now' }; export const RECORDS_FIELD = 'Records'; +export const RECORDS_PERCENTAGE_FIELD = 'RecordsPercentage'; export const FieldLabels: Record = { 'user_agent.name': BROWSER_FAMILY_LABEL, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index 72b4bd7919c3e..0be64677586c1 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -70,13 +70,23 @@ describe('Lens Attribute', () => { }); it('should return main y axis', function () { - expect(lnsAttr.getMainYAxis(layerConfig)).toEqual({ + expect(lnsAttr.getMainYAxis(layerConfig, 'layer0', '')).toEqual({ dataType: 'number', isBucketed: false, label: 'Pages loaded', - operationType: 'count', + operationType: 'formula', + params: { + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + formula: 'count() / overall_sum(count())', + isFormulaBroken: false, + }, + references: ['y-axis-column-layer0X4'], scale: 'ratio', - sourceField: 'Records', }); }); @@ -230,7 +240,15 @@ describe('Lens Attribute', () => { it('should return first layer', function () { expect(lnsAttr.getLayers()).toEqual({ layer0: { - columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'], + columnOrder: [ + 'x-axis-column-layer0', + 'y-axis-column-layer0', + 'y-axis-column-layer0X0', + 'y-axis-column-layer0X1', + 'y-axis-column-layer0X2', + 'y-axis-column-layer0X3', + 'y-axis-column-layer0X4', + ], columns: { 'x-axis-column-layer0': { dataType: 'number', @@ -253,16 +271,98 @@ describe('Lens Attribute', () => { }, 'y-axis-column-layer0': { dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, isBucketed: false, label: 'Pages loaded', + operationType: 'formula', + params: { + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + formula: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + isFormulaBroken: false, + }, + references: ['y-axis-column-layer0X4'], + scale: 'ratio', + }, + 'y-axis-column-layer0X0': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', operationType: 'count', scale: 'ratio', sourceField: 'Records', + }, + 'y-axis-column-layer0X1': { + customLabel: true, + dataType: 'number', filter: { language: 'kuery', query: 'transaction.type: page-load and processor.event: transaction and transaction.type : *', }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + 'y-axis-column-layer0X2': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: 'y-axis-column-layer0X1', + }, + references: ['y-axis-column-layer0X1'], + scale: 'ratio', + }, + 'y-axis-column-layer0X3': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'overall_sum', + references: ['y-axis-column-layer0X2'], + scale: 'ratio', + }, + 'y-axis-column-layer0X4': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: { + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + location: { + max: 30, + min: 0, + }, + name: 'divide', + text: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + type: 'function', + }, + }, + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + scale: 'ratio', }, }, incompleteColumns: {}, @@ -326,7 +426,16 @@ describe('Lens Attribute', () => { ]); expect(lnsAttr.layers.layer0).toEqual({ - columnOrder: ['x-axis-column-layer0', 'breakdown-column-layer0', 'y-axis-column-layer0'], + columnOrder: [ + 'x-axis-column-layer0', + 'breakdown-column-layer0', + 'y-axis-column-layer0', + 'y-axis-column-layer0X0', + 'y-axis-column-layer0X1', + 'y-axis-column-layer0X2', + 'y-axis-column-layer0X3', + 'y-axis-column-layer0X4', + ], columns: { 'breakdown-column-layer0': { dataType: 'string', @@ -353,7 +462,13 @@ describe('Lens Attribute', () => { operationType: 'range', params: { maxBars: 'auto', - ranges: [{ from: 0, label: '', to: 1000 }], + ranges: [ + { + from: 0, + label: '', + to: 1000, + }, + ], type: 'histogram', }, scale: 'interval', @@ -361,16 +476,98 @@ describe('Lens Attribute', () => { }, 'y-axis-column-layer0': { dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, isBucketed: false, label: 'Pages loaded', + operationType: 'formula', + params: { + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + formula: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + isFormulaBroken: false, + }, + references: ['y-axis-column-layer0X4'], + scale: 'ratio', + }, + 'y-axis-column-layer0X0': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', operationType: 'count', scale: 'ratio', sourceField: 'Records', + }, + 'y-axis-column-layer0X1': { + customLabel: true, + dataType: 'number', filter: { language: 'kuery', query: 'transaction.type: page-load and processor.event: transaction and transaction.type : *', }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + 'y-axis-column-layer0X2': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: 'y-axis-column-layer0X1', + }, + references: ['y-axis-column-layer0X1'], + scale: 'ratio', + }, + 'y-axis-column-layer0X3': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'overall_sum', + references: ['y-axis-column-layer0X2'], + scale: 'ratio', + }, + 'y-axis-column-layer0X4': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: { + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + location: { + max: 30, + min: 0, + }, + name: 'divide', + text: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + type: 'function', + }, + }, + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + scale: 'ratio', }, }, incompleteColumns: {}, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index eaf9c1c884a9d..5734cd1592692 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -35,10 +35,13 @@ import { USE_BREAK_DOWN_COLUMN, TERMS_COLUMN, REPORT_METRIC_FIELD, + RECORDS_FIELD, + RECORDS_PERCENTAGE_FIELD, } from './constants'; import { ColumnFilter, SeriesConfig, UrlFilter, URLReportDefinition } from '../types'; import { PersistableFilter } from '../../../../../../lens/common'; import { parseAbsoluteDate } from '../series_date_picker/date_range_picker'; +import { getDistributionInPercentageColumn } from './lens_columns/overall_column'; function getLayerReferenceName(layerId: string) { return `indexpattern-datasource-layer-${layerId}`; @@ -339,7 +342,7 @@ export class LensAttributes { return this.getTermsColumn(fieldName, columnLabel || label); } - if (fieldName === 'Records' || columnType === FILTER_RECORDS) { + if (fieldName === RECORDS_FIELD || columnType === FILTER_RECORDS) { return this.getRecordsColumn( columnLabel || label, colIndex !== undefined ? columnFilters?.[colIndex] : undefined, @@ -396,10 +399,14 @@ export class LensAttributes { } } - getMainYAxis(layerConfig: LayerConfig) { + getMainYAxis(layerConfig: LayerConfig, layerId: string, columnFilter: string) { const { sourceField, operationType, label } = layerConfig.seriesConfig.yAxisColumns[0]; - if (sourceField === 'Records' || !sourceField) { + if (sourceField === RECORDS_PERCENTAGE_FIELD) { + return getDistributionInPercentageColumn({ label, layerId, columnFilter }).main; + } + + if (sourceField === RECORDS_FIELD || !sourceField) { return this.getRecordsColumn(label); } @@ -412,9 +419,16 @@ export class LensAttributes { }); } - getChildYAxises(layerConfig: LayerConfig) { + getChildYAxises(layerConfig: LayerConfig, layerId?: string, columnFilter?: string) { const lensColumns: Record = {}; const yAxisColumns = layerConfig.seriesConfig.yAxisColumns; + const { sourceField: mainSourceField, label: mainLabel } = yAxisColumns[0]; + + if (mainSourceField === RECORDS_PERCENTAGE_FIELD && layerId) { + return getDistributionInPercentageColumn({ label: mainLabel, layerId, columnFilter }) + .supportingColumns; + } + // 1 means there is only main y axis if (yAxisColumns.length === 1) { return lensColumns; @@ -444,7 +458,7 @@ export class LensAttributes { label: label || 'Count of records', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: RECORDS_FIELD, filter: columnFilter, ...(timeScale ? { timeScale } : {}), } as CountIndexPatternColumn; @@ -546,13 +560,13 @@ export class LensAttributes { const layerId = `layer${index}`; const columnFilter = this.getLayerFilters(layerConfig, layerConfigs.length); const timeShift = this.getTimeShift(this.layerConfigs[0], layerConfig, index); - const mainYAxis = this.getMainYAxis(layerConfig); + const mainYAxis = this.getMainYAxis(layerConfig, layerId, columnFilter); layers[layerId] = { columnOrder: [ `x-axis-column-${layerId}`, ...(breakdown ? [`breakdown-column-${layerId}`] : []), `y-axis-column-${layerId}`, - ...Object.keys(this.getChildYAxises(layerConfig)), + ...Object.keys(this.getChildYAxises(layerConfig, layerId, columnFilter)), ], columns: { [`x-axis-column-${layerId}`]: this.getXAxis(layerConfig, layerId), @@ -572,7 +586,7 @@ export class LensAttributes { }), } : {}), - ...this.getChildYAxises(layerConfig), + ...this.getChildYAxises(layerConfig, layerId, columnFilter), }, incompleteColumns: {}, }; @@ -611,13 +625,13 @@ export class LensAttributes { }; } - parseFilters() {} - getJSON(): TypedLensByValueInput['attributes'] { const uniqueIndexPatternsIds = Array.from( new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)]) ); + const query = this.layerConfigs[0].seriesConfig.query; + return { title: 'Prefilled from exploratory view app', description: '', @@ -641,7 +655,7 @@ export class LensAttributes { }, }, visualization: this.visualization, - query: { query: '', language: 'kuery' }, + query: query || { query: '', language: 'kuery' }, filters: [], }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx new file mode 100644 index 0000000000000..05764517bc36f --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { TinymathAST } from '@kbn/tinymath'; +import { + DataType, + CountIndexPatternColumn, + MathIndexPatternColumn, + FormulaIndexPatternColumn, + OverallSumIndexPatternColumn, +} from '../../../../../../../lens/public'; + +export function getDistributionInPercentageColumn({ + label, + layerId, + columnFilter, +}: { + label?: string; + columnFilter?: string; + layerId: string; +}) { + const yAxisColId = `y-axis-column-${layerId}`; + + let lensFormula = 'count() / overall_sum(count())'; + + if (columnFilter) { + lensFormula = `count(kql='${columnFilter}') / overall_sum(count(kql='${columnFilter}'))`; + } + + const main: FormulaIndexPatternColumn = { + label: label || 'Percentage of records', + dataType: 'number' as DataType, + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { + formula: lensFormula, + isFormulaBroken: false, + format: { id: 'percent', params: { decimals: 0 } }, + }, + references: [`${yAxisColId}X4`], + }; + + const countColumn: CountIndexPatternColumn = { + label: 'Part of count() / overall_sum(count())', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: 'Records', + customLabel: true, + filter: { query: columnFilter ?? '', language: 'kuery' }, + }; + + const mathColumn: MathIndexPatternColumn = { + label: 'Part of count() / overall_sum(count())', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { tinymathAst: `${yAxisColId}X1` }, + references: [`${yAxisColId}X1`], + customLabel: true, + }; + + const overAllSumColumn: OverallSumIndexPatternColumn = { + label: 'Part of count() / overall_sum(count())', + dataType: 'number', + operationType: 'overall_sum', + isBucketed: false, + scale: 'ratio', + references: [`${yAxisColId}X2`], + customLabel: true, + }; + + const tinyMathColumn: MathIndexPatternColumn = { + label: 'Part of count() / overall_sum(count())', + dataType: 'number', + operationType: 'math', + isBucketed: false, + scale: 'ratio', + params: { + tinymathAst: ({ + type: 'function', + name: 'divide', + args: [`${yAxisColId}X0`, `${yAxisColId}X3`], + location: { min: 0, max: 30 }, + text: lensFormula, + } as unknown) as TinymathAST, + }, + references: [`${yAxisColId}X0`, `${yAxisColId}X3`], + customLabel: true, + }; + + const supportingColumns: Record< + string, + CountIndexPatternColumn | MathIndexPatternColumn | OverallSumIndexPatternColumn + > = { + [`${yAxisColId}X0`]: countColumn, + [`${yAxisColId}X1`]: countColumn, + [`${yAxisColId}X2`]: mathColumn, + [`${yAxisColId}X3`]: overAllSumColumn, + [`${yAxisColId}X4`]: tinyMathColumn, + }; + + return { main, supportingColumns }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts index b171edf2901d5..f34c8db6c197d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/data_distribution_config.ts @@ -6,7 +6,7 @@ */ import { ConfigProps, SeriesConfig } from '../../types'; -import { FieldLabels, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants'; +import { FieldLabels, REPORT_METRIC_FIELD, RECORDS_PERCENTAGE_FIELD } from '../constants'; import { buildPhraseFilter } from '../utils'; import { CLIENT_GEO_COUNTRY_NAME, @@ -49,7 +49,7 @@ export function getRumDistributionConfig({ indexPattern }: ConfigProps): SeriesC }, yAxisColumns: [ { - sourceField: RECORDS_FIELD, + sourceField: RECORDS_PERCENTAGE_FIELD, label: PAGES_LOADED_LABEL, }, ], @@ -91,5 +91,7 @@ export function getRumDistributionConfig({ indexPattern }: ConfigProps): SeriesC [SERVICE_NAME]: WEB_APPLICATION_LABEL, [TRANSACTION_DURATION]: PAGE_LOAD_TIME_LABEL, }, + // rum page load transactions are always less then 60 seconds + query: { query: 'transaction.duration.us < 60000000', language: 'kuery' }, }; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts index 9783f63f5b901..2522f0b2c2581 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts @@ -6,7 +6,7 @@ */ import { ConfigProps, SeriesConfig } from '../../types'; -import { FieldLabels, RECORDS_FIELD, REPORT_METRIC_FIELD } from '../constants'; +import { FieldLabels, REPORT_METRIC_FIELD, RECORDS_PERCENTAGE_FIELD } from '../constants'; import { buildExistsFilter } from '../utils'; import { MONITORS_DURATION_LABEL, PINGS_LABEL } from '../constants/labels'; @@ -23,7 +23,7 @@ export function getSyntheticsDistributionConfig({ }, yAxisColumns: [ { - sourceField: RECORDS_FIELD, + sourceField: RECORDS_PERCENTAGE_FIELD, label: PINGS_LABEL, }, ], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts index edf2a42415820..569d68ad4ebff 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts @@ -17,7 +17,15 @@ export const sampleAttribute = { indexpattern: { layers: { layer0: { - columnOrder: ['x-axis-column-layer0', 'y-axis-column-layer0'], + columnOrder: [ + 'x-axis-column-layer0', + 'y-axis-column-layer0', + 'y-axis-column-layer0X0', + 'y-axis-column-layer0X1', + 'y-axis-column-layer0X2', + 'y-axis-column-layer0X3', + 'y-axis-column-layer0X4', + ], columns: { 'x-axis-column-layer0': { sourceField: 'transaction.duration.us', @@ -34,8 +42,47 @@ export const sampleAttribute = { }, 'y-axis-column-layer0': { dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, isBucketed: false, label: 'Pages loaded', + operationType: 'formula', + params: { + format: { + id: 'percent', + params: { + decimals: 0, + }, + }, + formula: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + isFormulaBroken: false, + }, + references: ['y-axis-column-layer0X4'], + scale: 'ratio', + }, + 'y-axis-column-layer0X0': { + customLabel: true, + dataType: 'number', + filter: { + language: 'kuery', + query: + 'transaction.type: page-load and processor.event: transaction and transaction.type : *', + }, + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + 'y-axis-column-layer0X1': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', operationType: 'count', scale: 'ratio', sourceField: 'Records', @@ -45,6 +92,49 @@ export const sampleAttribute = { 'transaction.type: page-load and processor.event: transaction and transaction.type : *', }, }, + 'y-axis-column-layer0X2': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: 'y-axis-column-layer0X1', + }, + references: ['y-axis-column-layer0X1'], + scale: 'ratio', + }, + 'y-axis-column-layer0X3': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'overall_sum', + references: ['y-axis-column-layer0X2'], + scale: 'ratio', + }, + 'y-axis-column-layer0X4': { + customLabel: true, + dataType: 'number', + isBucketed: false, + label: 'Part of count() / overall_sum(count())', + operationType: 'math', + params: { + tinymathAst: { + args: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + location: { + max: 30, + min: 0, + }, + name: 'divide', + text: + "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + type: 'function', + }, + }, + references: ['y-axis-column-layer0X0', 'y-axis-column-layer0X3'], + scale: 'ratio', + }, }, incompleteColumns: {}, }, @@ -70,7 +160,7 @@ export const sampleAttribute = { }, ], }, - query: { query: '', language: 'kuery' }, + query: { query: 'transaction.duration.us < 60000000', language: 'kuery' }, filters: [], }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index ad7c654c9a168..717d98715453d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -63,6 +63,7 @@ export interface SeriesConfig { palette?: PaletteOutput; yTitle?: string; yConfig?: YConfig[]; + query?: { query: string; language: 'kuery' }; } export type URLReportDefinition = Record; From b774e37ea14bb2860a351994b5d69ca5d38d2f4c Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Tue, 29 Jun 2021 10:22:11 +0100 Subject: [PATCH 50/74] Update security telemetry allowlist. (#103471) --- .../security_solution/server/lib/telemetry/sender.test.ts | 4 ++++ .../plugins/security_solution/server/lib/telemetry/sender.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index f620027409d26..e6a53e520ee01 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -41,6 +41,7 @@ describe('TelemetryEventsSender', () => { version: '100', }, file: { + extension: '.exe', size: 3, created: 0, path: 'X', @@ -72,6 +73,7 @@ describe('TelemetryEventsSender', () => { name: 'foo.exe', nope: 'nope', executable: null, // null fields are never allowlisted + working_directory: '/some/usr/dir', }, Target: { process: { @@ -101,6 +103,7 @@ describe('TelemetryEventsSender', () => { version: '100', }, file: { + extension: '.exe', size: 3, created: 0, path: 'X', @@ -126,6 +129,7 @@ describe('TelemetryEventsSender', () => { }, process: { name: 'foo.exe', + working_directory: '/some/usr/dir', }, Target: { process: { diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 2b3c002a9b2ae..4f552b3edcda4 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -307,6 +307,7 @@ const allowlistProcessFields: AllowlistFields = { }, }, thread: true, + working_directory: true, }; // Allow list for event-related fields, which can also be nested under events[] @@ -322,6 +323,7 @@ const allowlistBaseEventFields: AllowlistFields = { }, event: true, file: { + extension: true, name: true, path: true, size: true, From 824463ace594b090ff49309ba082fd25ffa48c3c Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 29 Jun 2021 10:28:51 +0100 Subject: [PATCH 51/74] [ML] Fixing categorization tokens for multi-line messages (#103007) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../models/job_service/new_job/categorization/examples.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index 5fecb3d9eb1ec..abda62097dfb7 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -145,10 +145,11 @@ export function categorizationExamplesProvider({ for (let g = 0; g < sumLengths.length; g++) { if (t.start_offset <= sumLengths[g] + g) { const offset = g > 0 ? sumLengths[g - 1] + g : 0; + const start = t.start_offset - offset; tokensPerExample[g].push({ ...t, - start_offset: t.start_offset - offset, - end_offset: t.end_offset - offset, + start_offset: start, + end_offset: start + t.token.length, }); break; } From 2e00e9c11bbbde39eaeeda239390c11a6132978d Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 29 Jun 2021 11:02:17 +0100 Subject: [PATCH 52/74] [ML] Rare anomaly detection job wizard (#100390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ML] Rare anomaly detection job wizard * fixing fields selection * small improvements * adding event rate chart to summary step * [ML] Changes UI text for rare wizard. * improving detector summary * fixing translations * removing comments * fixing field selection * fixing advanced wizard * updating detector text * fixing bucketspan estimator * bug fixes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: István Zoltán Szabó --- x-pack/plugins/ml/common/constants/new_job.ts | 2 + .../job_creator/categorization_job_creator.ts | 2 +- .../jobs/new_job/common/job_creator/index.ts | 2 + .../new_job/common/job_creator/job_creator.ts | 3 + .../common/job_creator/job_creator_factory.ts | 4 + .../job_creator/population_job_creator.ts | 24 +-- .../common/job_creator/rare_job_creator.ts | 173 ++++++++++++++++++ .../new_job/common/job_creator/type_guards.ts | 8 +- .../common/job_creator/util/general.ts | 4 + .../advanced_view/metric_selection.tsx | 8 +- .../estimate_bucket_span.ts | 15 +- .../{split_field => by_field}/by_field.tsx | 14 +- .../components/by_field/index.ts | 8 + .../population_field/description.tsx | 32 ++++ .../components/population_field/index.ts | 8 + .../population_field/population_field.tsx | 93 ++++++++++ .../components/population_view/chart_grid.tsx | 2 +- .../population_view/metric_selection.tsx | 22 +-- .../metric_selection_summary.tsx | 12 +- .../rare_detector/detector_cards.tsx | 85 +++++++++ .../components/rare_detector/index.ts | 8 + .../rare_detector/rare_detector.tsx | 90 +++++++++ .../components/rare_field/description.tsx | 32 ++++ .../components/rare_field/index.ts | 8 + .../components/rare_field/rare_field.tsx | 81 ++++++++ .../rare_field/rare_field_select.tsx | 66 +++++++ .../rare_view/detector_description.tsx | 112 ++++++++++++ .../components/rare_view/index.ts | 9 + .../components/rare_view/metric_selection.tsx | 67 +++++++ .../rare_view/metric_selection_summary.tsx | 88 +++++++++ .../components/rare_view/rare_view.tsx | 54 ++++++ .../components/rare_view/settings.tsx | 38 ++++ .../components/split_field/description.tsx | 67 ++----- .../components/split_field/index.ts | 1 - .../components/split_field/split_field.tsx | 53 ++++-- .../components/split_field_select/index.ts | 8 + .../split_field_select.tsx | 0 .../pick_fields_step/pick_fields.tsx | 20 +- .../detector_chart/detector_chart.tsx | 2 + .../components/job_details/job_details.tsx | 6 +- .../preconfigured_job_redirect.ts | 3 + .../jobs/new_job/pages/job_type/page.tsx | 17 ++ .../new_job/pages/job_type/rare_job_icon.tsx | 23 +++ .../jobs/new_job/pages/new_job/page.tsx | 5 + .../routing/routes/new_job/wizard.tsx | 16 ++ 45 files changed, 1276 insertions(+), 119 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/rare_job_creator.ts rename x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/{split_field => by_field}/by_field.tsx (85%) create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/index.ts create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/index.ts create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/population_field.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/detector_cards.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/index.ts create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/rare_detector.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/index.ts create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/detector_description.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/index.ts create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/settings.tsx create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/index.ts rename x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/{split_field => split_field_select}/split_field_select.tsx (100%) create mode 100644 x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/rare_job_icon.tsx diff --git a/x-pack/plugins/ml/common/constants/new_job.ts b/x-pack/plugins/ml/common/constants/new_job.ts index 3a3b31c8e58c1..800b81d473726 100644 --- a/x-pack/plugins/ml/common/constants/new_job.ts +++ b/x-pack/plugins/ml/common/constants/new_job.ts @@ -11,6 +11,7 @@ export enum JOB_TYPE { POPULATION = 'population', ADVANCED = 'advanced', CATEGORIZATION = 'categorization', + RARE = 'rare', } export enum CREATED_BY_LABEL { @@ -18,6 +19,7 @@ export enum CREATED_BY_LABEL { MULTI_METRIC = 'multi-metric-wizard', POPULATION = 'population-wizard', CATEGORIZATION = 'categorization-wizard', + RARE = 'rare-wizard', APM_TRANSACTION = 'ml-module-apm-transaction', } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index eba5a954fadc2..ccea4ddf52ea3 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -66,7 +66,7 @@ export class CategorizationJobCreator extends JobCreator { eventRate: Field | null ) { if (count === null || rare === null || eventRate === null) { - return; + throw Error('event_rate field or count or rare aggregations missing'); } this._createCountDetector = () => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts index 0d4900e80b9f3..ec4e4c0e0c7ec 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts @@ -11,6 +11,7 @@ export { MultiMetricJobCreator } from './multi_metric_job_creator'; export { PopulationJobCreator } from './population_job_creator'; export { AdvancedJobCreator } from './advanced_job_creator'; export { CategorizationJobCreator } from './categorization_job_creator'; +export { RareJobCreator } from './rare_job_creator'; export { JobCreatorType, isSingleMetricJobCreator, @@ -18,5 +19,6 @@ export { isPopulationJobCreator, isAdvancedJobCreator, isCategorizationJobCreator, + isRareJobCreator, } from './type_guards'; export { jobCreatorFactory } from './job_creator_factory'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index d4a410bcda24c..45e7247f0bd85 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -395,6 +395,9 @@ export class JobCreator { // change the detector to be a non-zer or non-null count or sum. // note, the aggregations will always be a standard count or sum and not a non-null or non-zero version this._detectors.forEach((d, i) => { + if (this._aggs[i] === undefined) { + return; + } switch (this._aggs[i].id) { case ML_JOB_AGGREGATION.COUNT: d.function = this._sparseData diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts index e815ddfcd97c3..7b9305e42dfdd 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts @@ -12,6 +12,7 @@ import { PopulationJobCreator } from './population_job_creator'; import { AdvancedJobCreator } from './advanced_job_creator'; import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { CategorizationJobCreator } from './categorization_job_creator'; +import { RareJobCreator } from './rare_job_creator'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; @@ -37,6 +38,9 @@ export const jobCreatorFactory = (jobType: JOB_TYPE) => ( case JOB_TYPE.CATEGORIZATION: jc = CategorizationJobCreator; break; + case JOB_TYPE.RARE: + jc = RareJobCreator; + break; default: jc = SingleMetricJobCreator; break; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts index 14570fa039ccf..24b3192231211 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts @@ -22,7 +22,7 @@ import { IndexPattern } from '../../../../../../../../../src/plugins/data/public export class PopulationJobCreator extends JobCreator { // a population job has one overall over (split) field, which is the same for all detectors // each detector has an optional by field - private _splitField: SplitField = null; + private _populatonField: SplitField = null; private _byFields: SplitField[] = []; protected _type: JOB_TYPE = JOB_TYPE.POPULATION; @@ -65,27 +65,27 @@ export class PopulationJobCreator extends JobCreator { } // add an over field to all detectors - public setSplitField(field: SplitField) { - this._splitField = field; + public setPopulationField(field: SplitField) { + this._populatonField = field; - if (this._splitField === null) { - this.removeSplitField(); + if (this._populatonField === null) { + this.removePopulationField(); } else { for (let i = 0; i < this._detectors.length; i++) { - this._detectors[i].over_field_name = this._splitField.id; + this._detectors[i].over_field_name = this._populatonField.id; } } } // remove over field from all detectors - public removeSplitField() { + public removePopulationField() { this._detectors.forEach((d) => { delete d.over_field_name; }); } - public get splitField(): SplitField { - return this._splitField; + public get populationField(): SplitField { + return this._populatonField; } public addDetector(agg: Aggregation, field: Field) { @@ -112,8 +112,8 @@ export class PopulationJobCreator extends JobCreator { private _createDetector(agg: Aggregation, field: Field) { const dtr: Detector = createBasicDetector(agg, field); - if (this._splitField !== null) { - dtr.over_field_name = this._splitField.id; + if (this._populatonField !== null) { + dtr.over_field_name = this._populatonField.id; } return dtr; } @@ -143,7 +143,7 @@ export class PopulationJobCreator extends JobCreator { if (detectors.length) { if (detectors[0].overField !== null) { - this.setSplitField(detectors[0].overField); + this.setPopulationField(detectors[0].overField); } } detectors.forEach((d, i) => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/rare_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/rare_job_creator.ts new file mode 100644 index 0000000000000..73050dc4b7834 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/rare_job_creator.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; +import { JobCreator } from './job_creator'; +import { Field, SplitField, Aggregation } from '../../../../../../common/types/fields'; +import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs'; +import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job'; +import { getRichDetectors } from './util/general'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; +import { isSparseDataJob } from './util/general'; +import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; + +export class RareJobCreator extends JobCreator { + private _rareField: Field | null = null; + private _populationField: SplitField = null; + private _splitField: SplitField = null; + + protected _type: JOB_TYPE = JOB_TYPE.RARE; + private _rareInPopulation: boolean = false; + private _frequentlyRare: boolean = false; + private _rareAgg: Aggregation; + private _freqRareAgg: Aggregation; + + constructor( + indexPattern: IndexPattern, + savedSearch: SavedSearchSavedObject | null, + query: object + ) { + super(indexPattern, savedSearch, query); + this.createdBy = CREATED_BY_LABEL.RARE; + this._wizardInitialized$.next(true); + this._rareAgg = {} as Aggregation; + this._freqRareAgg = {} as Aggregation; + } + + public setDefaultDetectorProperties(rare: Aggregation | null, freqRare: Aggregation | null) { + if (rare === null || freqRare === null) { + throw Error('rare or freq_rare aggregations missing'); + } + this._rareAgg = rare; + this._freqRareAgg = freqRare; + } + + public setRareField(field: Field | null) { + this._rareField = field; + + if (field === null) { + this.removePopulationField(); + this.removeSplitField(); + this._removeDetector(0); + this._detectors.length = 0; + this._fields.length = 0; + return; + } + + const agg = this._frequentlyRare ? this._freqRareAgg : this._rareAgg; + + const dtr: Detector = { + function: agg.id, + }; + if (this._detectors.length === 0) { + this._addDetector(dtr, agg, field); + } else { + this._editDetector(dtr, agg, field, 0); + } + + this._detectors[0].by_field_name = field.id; + } + + public get rareField() { + return this._rareField; + } + + public get rareInPopulation() { + return this._rareInPopulation; + } + + public set rareInPopulation(bool: boolean) { + this._rareInPopulation = bool; + if (bool === false) { + this.removePopulationField(); + } + } + + public get frequentlyRare() { + return this._frequentlyRare; + } + + public set frequentlyRare(bool: boolean) { + this._frequentlyRare = bool; + if (this._detectors.length) { + const agg = bool ? this._freqRareAgg : this._rareAgg; + this._detectors[0].function = agg.id; + this._aggs[0] = agg; + } + } + + // set the population field, applying it to each detector + public setPopulationField(field: SplitField) { + this._populationField = field; + + if (this._populationField === null) { + this.removePopulationField(); + } else { + for (let i = 0; i < this._detectors.length; i++) { + this._detectors[i].over_field_name = this._populationField.id; + } + } + } + + public removePopulationField() { + this._populationField = null; + this._detectors.forEach((d) => { + delete d.over_field_name; + }); + } + + public get populationField(): SplitField { + return this._populationField; + } + + // set the split field, applying it to each detector + public setSplitField(field: SplitField) { + this._splitField = field; + + if (this._splitField === null) { + this.removeSplitField(); + } else { + for (let i = 0; i < this._detectors.length; i++) { + this._detectors[i].partition_field_name = this._splitField.id; + } + } + } + + public removeSplitField() { + this._detectors.forEach((d) => { + delete d.partition_field_name; + }); + } + + public get splitField(): SplitField { + return this._splitField; + } + + public cloneFromExistingJob(job: Job, datafeed: Datafeed) { + this._overrideConfigs(job, datafeed); + this.createdBy = CREATED_BY_LABEL.RARE; + this._sparseData = isSparseDataJob(job, datafeed); + const detectors = getRichDetectors(job, datafeed, this.additionalFields, false); + + this.removeSplitField(); + this.removePopulationField(); + this.removeAllDetectors(); + + if (detectors.length) { + this.setRareField(detectors[0].byField); + this.frequentlyRare = detectors[0].agg?.id === ML_JOB_AGGREGATION.FREQ_RARE; + + if (detectors[0].overField !== null) { + this.setPopulationField(detectors[0].overField); + this.rareInPopulation = true; + } + if (detectors[0].partitionField !== null) { + this.setSplitField(detectors[0].partitionField); + } + } + } +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts index a3998a0005480..902d67b82a9e3 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts @@ -10,6 +10,7 @@ import { MultiMetricJobCreator } from './multi_metric_job_creator'; import { PopulationJobCreator } from './population_job_creator'; import { AdvancedJobCreator } from './advanced_job_creator'; import { CategorizationJobCreator } from './categorization_job_creator'; +import { RareJobCreator } from './rare_job_creator'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; export type JobCreatorType = @@ -17,7 +18,8 @@ export type JobCreatorType = | MultiMetricJobCreator | PopulationJobCreator | AdvancedJobCreator - | CategorizationJobCreator; + | CategorizationJobCreator + | RareJobCreator; export function isSingleMetricJobCreator( jobCreator: JobCreatorType @@ -46,3 +48,7 @@ export function isCategorizationJobCreator( ): jobCreator is CategorizationJobCreator { return jobCreator.type === JOB_TYPE.CATEGORIZATION; } + +export function isRareJobCreator(jobCreator: JobCreatorType): jobCreator is RareJobCreator { + return jobCreator.type === JOB_TYPE.RARE; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index bab6800c08335..78903e64686f5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -311,6 +311,10 @@ export function getJobCreatorTitle(jobCreator: JobCreatorType) { return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.categorization', { defaultMessage: 'Categorization', }); + case JOB_TYPE.RARE: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.rare', { + defaultMessage: 'Rare', + }); default: return ''; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/metric_selection.tsx index 8f53e1283faa0..36fd27aaba27c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/metric_selection.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, FC, useContext, useState } from 'react'; +import React, { Fragment, FC, useContext, useState, useEffect } from 'react'; import { JobCreatorContext } from '../../../job_creator_context'; import { AdvancedJobCreator } from '../../../../../common/job_creator'; @@ -33,12 +33,16 @@ const emptyRichDetector: RichDetector = { }; export const AdvancedDetectors: FC = ({ setIsValid }) => { - const { jobCreator: jc, jobCreatorUpdate } = useContext(JobCreatorContext); + const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); const jobCreator = jc as AdvancedJobCreator; const { fields, aggs } = newJobCapsService; const [modalPayload, setModalPayload] = useState(null); + useEffect(() => { + setIsValid(jobCreator.detectors.length > 0); + }, [jobCreatorUpdated]); + function closeModal() { setModalPayload(null); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 85083146c1378..67673901494c7 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -14,6 +14,7 @@ import { isMultiMetricJobCreator, isPopulationJobCreator, isAdvancedJobCreator, + isRareJobCreator, } from '../../../../../common/job_creator'; import { ml } from '../../../../../../../services/ml_api_service'; import { useMlContext } from '../../../../../../../contexts/ml'; @@ -45,11 +46,17 @@ export function useEstimateBucketSpan() { indicesOptions: jobCreator.datafeedConfig.indices_options, }; - if ( - (isMultiMetricJobCreator(jobCreator) || isPopulationJobCreator(jobCreator)) && - jobCreator.splitField !== null - ) { + if (isMultiMetricJobCreator(jobCreator) && jobCreator.splitField !== null) { data.splitField = jobCreator.splitField.id; + } else if (isPopulationJobCreator(jobCreator) && jobCreator.populationField !== null) { + data.splitField = jobCreator.populationField.id; + } else if (isRareJobCreator(jobCreator)) { + data.fields = [null]; + if (jobCreator.populationField) { + data.splitField = jobCreator.populationField.id; + } else { + data.splitField = jobCreator.rareField?.id; + } } else if (isAdvancedJobCreator(jobCreator)) { jobCreator.richDetectors.some((d) => { if (d.partitionField !== null) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/by_field.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx rename to x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/by_field.tsx index b197b950bbe28..17577641fe183 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/by_field.tsx @@ -8,14 +8,14 @@ import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { SplitFieldSelect } from './split_field_select'; +import { SplitFieldSelect } from '../split_field_select'; import { JobCreatorContext } from '../../../job_creator_context'; import { Field } from '../../../../../../../../../common/types/fields'; import { newJobCapsService, filterCategoryFields, } from '../../../../../../../services/new_job_capabilities/new_job_capabilities_service'; -import { MultiMetricJobCreator, PopulationJobCreator } from '../../../../../common/job_creator'; +import { PopulationJobCreator } from '../../../../../common/job_creator'; interface Props { detectorIndex: number; @@ -69,18 +69,18 @@ export const ByFieldSelector: FC = ({ detectorIndex }) => { ); }; -// remove the split (over) field from the by field options +// remove the population (over) field from the by field options function useFilteredCategoryFields( allCategoryFields: Field[], - jobCreator: MultiMetricJobCreator | PopulationJobCreator, + jobCreator: PopulationJobCreator, jobCreatorUpdated: number ) { const [fields, setFields] = useState(allCategoryFields); useEffect(() => { - const sf = jobCreator.splitField; - if (sf !== null) { - setFields(allCategoryFields.filter((f) => f.name !== sf.name)); + const pf = jobCreator.populationField; + if (pf !== null) { + setFields(allCategoryFields.filter(({ name }) => name !== pf.name)); } else { setFields(allCategoryFields); } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/index.ts new file mode 100644 index 0000000000000..542a483c374b8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/by_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ByFieldSelector } from './by_field'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.tsx new file mode 100644 index 0000000000000..6abbe77fc35aa --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/description.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; + +export const Description: FC = memo(({ children }) => { + const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.populationField.title', { + defaultMessage: 'Population field', + }); + return ( + {title}} + description={ + + } + > + + <>{children} + + + ); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/index.ts new file mode 100644 index 0000000000000..02e5236fd54e3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { PopulationFieldSelector } from './population_field'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/population_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/population_field.tsx new file mode 100644 index 0000000000000..5b69d5b63b534 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_field/population_field.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; + +import { SplitFieldSelect } from '../split_field_select'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { Field } from '../../../../../../../../../common/types/fields'; +import { + newJobCapsService, + filterCategoryFields, +} from '../../../../../../../services/new_job_capabilities/new_job_capabilities_service'; +import { Description } from './description'; +import { + PopulationJobCreator, + RareJobCreator, + isPopulationJobCreator, +} from '../../../../../common/job_creator'; + +export const PopulationFieldSelector: FC = () => { + const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as PopulationJobCreator | RareJobCreator; + + const runtimeCategoryFields = useMemo(() => filterCategoryFields(jobCreator.runtimeFields), []); + const allCategoryFields = useMemo( + () => [...newJobCapsService.categoryFields, ...runtimeCategoryFields], + [] + ); + const categoryFields = useFilteredCategoryFields( + allCategoryFields, + jobCreator, + jobCreatorUpdated + ); + + const [populationField, setPopulationField] = useState(jobCreator.populationField); + + useEffect(() => { + jobCreator.setPopulationField(populationField); + // add the split field to the influencers + if ( + populationField !== null && + jobCreator.influencers.includes(populationField.name) === false + ) { + jobCreator.addInfluencer(populationField.name); + } + jobCreatorUpdate(); + }, [populationField]); + + useEffect(() => { + setPopulationField(jobCreator.populationField); + }, [jobCreatorUpdated]); + + return ( + + + + ); +}; + +// remove the rare (by) field from the by field options in the rare wizard +function useFilteredCategoryFields( + allCategoryFields: Field[], + jobCreator: PopulationJobCreator | RareJobCreator, + jobCreatorUpdated: number +) { + const [fields, setFields] = useState(allCategoryFields); + + useEffect(() => { + if (isPopulationJobCreator(jobCreator)) { + setFields(allCategoryFields); + } else { + const rf = jobCreator.rareField; + const sf = jobCreator.splitField; + if (rf !== null || sf !== null) { + setFields(allCategoryFields.filter(({ name }) => name !== rf?.name && name !== sf?.name)); + } else { + setFields(allCategoryFields); + } + } + }, [jobCreatorUpdated]); + + return fields; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/chart_grid.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/chart_grid.tsx index 6fa90fa06a1e5..1e355b0fb2dd9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/chart_grid.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/chart_grid.tsx @@ -15,7 +15,7 @@ import { ModelItem, Anomaly } from '../../../../../common/results_loader'; import { JOB_TYPE } from '../../../../../../../../../common/constants/new_job'; import { SplitCards, useAnimateSplit } from '../split_cards'; import { DetectorTitle } from '../detector_title'; -import { ByFieldSelector } from '../split_field'; +import { ByFieldSelector } from '../by_field'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; type DetectorFieldValues = Record; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index 1be487d5b7eec..1f669fea655b7 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -17,7 +17,7 @@ import { Field, AggFieldPair } from '../../../../../../../../../common/types/fie import { sortFields } from '../../../../../../../../../common/util/fields_utils'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; -import { SplitFieldSelector } from '../split_field'; +import { PopulationFieldSelector } from '../population_field'; import { ChartGrid } from './chart_grid'; import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; @@ -51,7 +51,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { const [end, setEnd] = useState(jobCreator.end); const [bucketSpanMs, setBucketSpanMs] = useState(jobCreator.bucketSpanMs); const [chartSettings, setChartSettings] = useState(defaultChartSettings); - const [splitField, setSplitField] = useState(jobCreator.splitField); + const [populationField, setPopulationField] = useState(jobCreator.populationField); const [fieldValuesPerDetector, setFieldValuesPerDetector] = useState({}); const [byFieldsUpdated, setByFieldsUpdated] = useReducer<(s: number, action: any) => number>( (s) => s + 1, @@ -108,7 +108,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { // if the split field or by fields have changed useEffect(() => { loadCharts(); - }, [JSON.stringify(fieldValuesPerDetector), splitField, pageReady]); + }, [JSON.stringify(fieldValuesPerDetector), populationField, pageReady]); // watch for change in jobCreator useEffect(() => { @@ -123,7 +123,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { loadCharts(); } - setSplitField(jobCreator.splitField); + setPopulationField(jobCreator.populationField); // update by fields and their by fields let update = false; @@ -146,7 +146,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { // changes to fieldValues here will trigger the card effect via setFieldValuesPerDetector useEffect(() => { loadFieldExamples(); - }, [splitField, byFieldsUpdated]); + }, [populationField, byFieldsUpdated]); async function loadCharts() { if (allDataReady()) { @@ -158,7 +158,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { jobCreator.start, jobCreator.end, aggFieldPairList, - jobCreator.splitField, + jobCreator.populationField, cs.intervalMs, jobCreator.runtimeMappings, jobCreator.datafeedConfig.indices_options @@ -225,14 +225,14 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { return ( - - {splitField !== null && } + + {populationField !== null && } - {splitField !== null && ( + {populationField !== null && ( = ({ setIsValid }) => { loading={loadingData} /> )} - {splitField !== null && ( + {populationField !== null && ( { if (allDataReady()) { loadCharts(); } - }, [JSON.stringify(fieldValuesPerDetector), jobCreator.splitField]); + }, [JSON.stringify(fieldValuesPerDetector), jobCreator.populationField]); // watch for changes in split field or by fields. // load example field values // changes to fieldValues here will trigger the card effect via setFieldValuesPerDetector useEffect(() => { loadFieldExamples(); - }, [jobCreator.splitField]); + }, [jobCreator.populationField]); async function loadCharts() { if (allDataReady()) { @@ -76,7 +76,7 @@ export const PopulationDetectorsSummary: FC = () => { jobCreator.start, jobCreator.end, aggFieldPairList, - jobCreator.splitField, + jobCreator.populationField, cs.intervalMs, jobCreator.runtimeMappings, jobCreator.datafeedConfig.indices_options @@ -143,18 +143,18 @@ export const PopulationDetectorsSummary: FC = () => { return ( - {jobCreator.splitField !== null && ( + {jobCreator.populationField !== null && ( void; + isSelected: boolean; +} + +export const RareCard: FC = ({ onClick, isSelected }) => ( + + + + + } + selectable={{ onClick, isSelected }} + /> + +); + +export const RareInPopulationCard: FC = ({ onClick, isSelected }) => ( + + + + + } + selectable={{ onClick, isSelected }} + /> + +); + +export const FrequentlyRareInPopulationCard: FC = ({ onClick, isSelected }) => ( + + + + + } + selectable={{ onClick, isSelected }} + /> + +); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/index.ts new file mode 100644 index 0000000000000..2e5ca95766527 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { RareDetector } from './rare_detector'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/rare_detector.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/rare_detector.tsx new file mode 100644 index 0000000000000..a7e5b22d2eac0 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_detector/rare_detector.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { JobCreatorContext } from '../../../job_creator_context'; +import { RareJobCreator } from '../../../../../common/job_creator'; +import { RareCard, RareInPopulationCard, FrequentlyRareInPopulationCard } from './detector_cards'; +import { RARE_DETECTOR_TYPE } from '../rare_view'; + +interface Props { + onChange(d: RARE_DETECTOR_TYPE): void; +} + +export const RareDetector: FC = ({ onChange }) => { + const { jobCreator: jc, jobCreatorUpdate } = useContext(JobCreatorContext); + const jobCreator = jc as RareJobCreator; + const [rareDetectorType, setRareDetectorType] = useState(null); + + useEffect(() => { + if (jobCreator.rareField !== null) { + if (jobCreator.populationField === null) { + setRareDetectorType(RARE_DETECTOR_TYPE.RARE); + } else { + setRareDetectorType( + jobCreator.frequentlyRare + ? RARE_DETECTOR_TYPE.FREQ_RARE_POPULATION + : RARE_DETECTOR_TYPE.RARE_POPULATION + ); + } + } else { + setRareDetectorType(RARE_DETECTOR_TYPE.RARE); + } + }, []); + + useEffect(() => { + if (rareDetectorType !== null) { + onChange(rareDetectorType); + if (rareDetectorType === RARE_DETECTOR_TYPE.RARE && jobCreator.populationField !== null) { + jobCreator.removePopulationField(); + } + jobCreator.frequentlyRare = rareDetectorType === RARE_DETECTOR_TYPE.FREQ_RARE_POPULATION; + jobCreatorUpdate(); + } + }, [rareDetectorType]); + + function onRareSelection() { + setRareDetectorType(RARE_DETECTOR_TYPE.RARE); + } + function onRareInPopulationSelection() { + setRareDetectorType(RARE_DETECTOR_TYPE.RARE_POPULATION); + } + function onFreqRareInPopulationSelection() { + setRareDetectorType(RARE_DETECTOR_TYPE.FREQ_RARE_POPULATION); + } + + return ( + <> + +

    + +

    +
    + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.tsx new file mode 100644 index 0000000000000..4c3b547d580d7 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/description.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; + +export const Description: FC = memo(({ children }) => { + const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.splitRareField.title', { + defaultMessage: 'Rare field', + }); + return ( + {title}} + description={ + + } + > + + <>{children} + + + ); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/index.ts new file mode 100644 index 0000000000000..bd467f22c1c5a --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { RareFieldSelector } from './rare_field'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field.tsx new file mode 100644 index 0000000000000..200d58f3b0171 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; + +import { RareFieldSelect } from './rare_field_select'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { + newJobCapsService, + filterCategoryFields, +} from '../../../../../../../services/new_job_capabilities/new_job_capabilities_service'; +import { Description } from './description'; +import { Field } from '../../../../../../../../../common/types/fields'; +import { RareJobCreator } from '../../../../../common/job_creator'; + +export const RareFieldSelector: FC = () => { + const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as RareJobCreator; + + const runtimeCategoryFields = useMemo(() => filterCategoryFields(jobCreator.runtimeFields), []); + const allCategoryFields = useMemo( + () => [...newJobCapsService.categoryFields, ...runtimeCategoryFields], + [] + ); + const categoryFields = useFilteredCategoryFields( + allCategoryFields, + jobCreator, + jobCreatorUpdated + ); + + const [rareField, setRareField] = useState(jobCreator.rareField); + + useEffect(() => { + jobCreator.setRareField(rareField); + // add the split field to the influencers + if (rareField !== null && jobCreator.influencers.includes(rareField.name) === false) { + jobCreator.addInfluencer(rareField.name); + } + jobCreatorUpdate(); + }, [rareField]); + + useEffect(() => { + setRareField(jobCreator.rareField); + }, [jobCreatorUpdated]); + + return ( + + + + ); +}; + +// remove the rare (by) field from the by field options in the rare wizard +function useFilteredCategoryFields( + allCategoryFields: Field[], + jobCreator: RareJobCreator, + jobCreatorUpdated: number +) { + const [fields, setFields] = useState(allCategoryFields); + + useEffect(() => { + const pf = jobCreator.populationField; + const sf = jobCreator.splitField; + if (pf !== null || sf !== null) { + setFields(allCategoryFields.filter(({ name }) => name !== pf?.name && name !== sf?.name)); + } else { + setFields(allCategoryFields); + } + }, [jobCreatorUpdated]); + + return fields; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx new file mode 100644 index 0000000000000..3868554215d89 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_field/rare_field_select.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { Field, SplitField } from '../../../../../../../../../common/types/fields'; + +interface DropDownLabel { + label: string; + field: Field; +} + +interface Props { + fields: Field[]; + changeHandler(f: SplitField): void; + selectedField: SplitField; + testSubject?: string; + placeholder?: string; +} + +export const RareFieldSelect: FC = ({ + fields, + changeHandler, + selectedField, + testSubject, + placeholder, +}) => { + const options: EuiComboBoxOptionOption[] = fields.map( + (f) => + ({ + label: f.name, + field: f, + } as DropDownLabel) + ); + + const selection: EuiComboBoxOptionOption[] = []; + if (selectedField !== null) { + selection.push({ label: selectedField.name, field: selectedField } as DropDownLabel); + } + + function onChange(selectedOptions: EuiComboBoxOptionOption[]) { + const option = selectedOptions[0] as DropDownLabel; + if (typeof option !== 'undefined') { + changeHandler(option.field); + } else { + changeHandler(null); + } + } + + return ( + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/detector_description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/detector_description.tsx new file mode 100644 index 0000000000000..dcd6d859ff868 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/detector_description.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { FC, useContext, useEffect, useState } from 'react'; +import { EuiCallOut } from '@elastic/eui'; + +import { JobCreatorContext } from '../../../job_creator_context'; +import { RareJobCreator } from '../../../../../common/job_creator'; +import { RARE_DETECTOR_TYPE } from './rare_view'; + +interface Props { + detectorType: RARE_DETECTOR_TYPE; +} + +export const DetectorDescription: FC = ({ detectorType }) => { + const { jobCreator: jc, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as RareJobCreator; + const [description, setDescription] = useState(null); + + useEffect(() => { + const desc = createDetectorDescription(jobCreator, detectorType); + setDescription(desc); + }, [jobCreatorUpdated]); + + if (description === null) { + return null; + } + + return ( + + +
      + {description.map((d) => ( +
    • {d}
    • + ))} +
    +
    + ); +}; + +function createDetectorDescription(jobCreator: RareJobCreator, detectorType: RARE_DETECTOR_TYPE) { + if (jobCreator.rareField === null) { + return null; + } + + const rareFieldName = jobCreator.rareField.id; + const populationFieldName = jobCreator.populationField?.id; + const splitFieldName = jobCreator.splitField?.id; + + const beginningSummary = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.rareField.plainText.beginningSummary', + { + defaultMessage: 'detects rare values of {rareFieldName}', + values: { rareFieldName }, + } + ); + + const beginningSummaryFreq = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.rareField.plainText.beginningSummaryFreq', + { + defaultMessage: 'detects frequently rare values of {rareFieldName}', + values: { rareFieldName }, + } + ); + + const population = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.rareField.plainText.population', + { + defaultMessage: 'compared to the population of {populationFieldName}', + values: { populationFieldName }, + } + ); + + const split = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.rareField.plainText.split', { + defaultMessage: 'for each value of {splitFieldName}', + values: { splitFieldName }, + }); + + const desc = []; + + if (detectorType === RARE_DETECTOR_TYPE.FREQ_RARE_POPULATION) { + desc.push(beginningSummaryFreq); + } else { + desc.push(beginningSummary); + } + + if (populationFieldName !== undefined) { + desc.push(population); + } + + if (splitFieldName !== undefined) { + desc.push(split); + } + + return desc; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/index.ts new file mode 100644 index 0000000000000..285adba0a414a --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { RareView } from './rare_view'; +export { RARE_DETECTOR_TYPE } from './rare_view'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection.tsx new file mode 100644 index 0000000000000..1c1a8e83c478b --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { RareFieldSelector } from '../rare_field'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { RareJobCreator } from '../../../../../common/job_creator'; +import { RareDetector } from '../rare_detector'; +import { PopulationFieldSelector } from '../population_field'; +import { DetectorDescription } from './detector_description'; +import { RARE_DETECTOR_TYPE } from './rare_view'; + +interface Props { + setIsValid: (na: boolean) => void; + setRareDetectorType(t: RARE_DETECTOR_TYPE): void; + rareDetectorType: RARE_DETECTOR_TYPE; +} + +export const RareDetectors: FC = ({ setIsValid, rareDetectorType, setRareDetectorType }) => { + const { jobCreator: jc, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as RareJobCreator; + const [detectorValid, setDetectorValid] = useState(false); + + useEffect(() => { + let valid = false; + if (jobCreator.rareField !== null) { + if (rareDetectorType === RARE_DETECTOR_TYPE.RARE) { + // Rare only requires a rare field to be set + valid = true; + } else if (jobCreator.populationField !== null) { + // all others need a need the population field to be set + valid = true; + } + } + setIsValid(valid); + setDetectorValid(valid); + }, [jobCreatorUpdated]); + + return ( + <> + + <> + + + + + + + {rareDetectorType !== RARE_DETECTOR_TYPE.RARE && } + + + {detectorValid && ( + <> + + + + )} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx new file mode 100644 index 0000000000000..02566474512ee --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { RareJobCreator } from '../../../../../common/job_creator'; +import { Results, Anomaly } from '../../../../../common/results_loader'; +import { LineChartPoint } from '../../../../../common/chart_loader'; +import { EventRateChart } from '../../../charts/event_rate_chart'; + +import { RARE_DETECTOR_TYPE } from './rare_view'; +import { DetectorDescription } from './detector_description'; + +const DTR_IDX = 0; +interface Props { + rareDetectorType: RARE_DETECTOR_TYPE; +} + +export const RareDetectorsSummary: FC = ({ rareDetectorType }) => { + const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( + JobCreatorContext + ); + const jobCreator = jc as RareJobCreator; + + const [loadingData, setLoadingData] = useState(false); + const [anomalyData, setAnomalyData] = useState([]); + const [eventRateChartData, setEventRateChartData] = useState([]); + const [jobIsRunning, setJobIsRunning] = useState(false); + + function setResultsWrapper(results: Results) { + const anomalies = results.anomalies[DTR_IDX]; + if (anomalies !== undefined) { + setAnomalyData(anomalies); + } + } + + function watchProgress(progress: number) { + setJobIsRunning(progress > 0); + } + + useEffect(() => { + // subscribe to progress and results + const resultsSubscription = resultsLoader.subscribeToResults(setResultsWrapper); + jobCreator.subscribeToProgress(watchProgress); + loadChart(); + return () => { + resultsSubscription.unsubscribe(); + }; + }, []); + + async function loadChart() { + setLoadingData(true); + try { + const resp = await chartLoader.loadEventRateChart( + jobCreator.start, + jobCreator.end, + chartInterval.getInterval().asMilliseconds(), + jobCreator.runtimeMappings ?? undefined, + jobCreator.datafeedConfig.indices_options + ); + setEventRateChartData(resp); + } catch (error) { + setEventRateChartData([]); + } + setLoadingData(false); + } + + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx new file mode 100644 index 0000000000000..d67cac8d0fc5c --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useEffect, useState } from 'react'; +import { EuiHorizontalRule } from '@elastic/eui'; + +import { RareDetectors } from './metric_selection'; +import { RareDetectorsSummary } from './metric_selection_summary'; +import { RareSettings } from './settings'; + +export enum RARE_DETECTOR_TYPE { + RARE, + RARE_POPULATION, + FREQ_RARE_POPULATION, +} + +interface Props { + isActive: boolean; + setCanProceed?: (proceed: boolean) => void; +} + +export const RareView: FC = ({ isActive, setCanProceed }) => { + const [rareFieldValid, setRareFieldValid] = useState(false); + const [settingsValid, setSettingsValid] = useState(false); + const [rareDetectorType, setRareDetectorType] = useState(RARE_DETECTOR_TYPE.RARE); + + useEffect(() => { + if (typeof setCanProceed === 'function') { + setCanProceed(rareFieldValid && settingsValid); + } + }, [rareFieldValid, settingsValid]); + + return isActive === false ? ( + + ) : ( + <> + + {rareFieldValid && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/settings.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/settings.tsx new file mode 100644 index 0000000000000..88bf04322d182 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/settings.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { BucketSpan } from '../bucket_span'; +import { SplitFieldSelector } from '../split_field'; +import { Influencers } from '../influencers'; + +interface Props { + setIsValid: (proceed: boolean) => void; +} + +export const RareSettings: FC = ({ setIsValid }) => { + return ( + <> + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/description.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/description.tsx index 9f569de09864b..0d5cd8bda3d0d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/description.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/description.tsx @@ -10,52 +10,23 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; -import { JOB_TYPE } from '../../../../../../../../../common/constants/new_job'; - -interface Props { - jobType: JOB_TYPE; -} - -export const Description: FC = memo(({ children, jobType }) => { - if (jobType === JOB_TYPE.MULTI_METRIC) { - const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.splitField.title', { - defaultMessage: 'Split field', - }); - return ( - {title}} - description={ - - } - > - - <>{children} - - - ); - } else if (jobType === JOB_TYPE.POPULATION) { - const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.populationField.title', { - defaultMessage: 'Population field', - }); - return ( - {title}} - description={ - - } - > - - <>{children} - - - ); - } else { - return null; - } +export const Description: FC = memo(({ children }) => { + const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.splitField.title', { + defaultMessage: 'Split field', + }); + return ( + {title}} + description={ + + } + > + + <>{children} + + + ); }); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/index.ts index d8d37e84c2c88..9e1698053f290 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/index.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { ByFieldSelector } from './by_field'; export { SplitFieldSelector } from './split_field'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx index 9837fe924fb01..a007a6ee826c4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx @@ -7,30 +7,34 @@ import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; -import { SplitFieldSelect } from './split_field_select'; +import { SplitFieldSelect } from '../split_field_select'; import { JobCreatorContext } from '../../../job_creator_context'; import { newJobCapsService, filterCategoryFields, } from '../../../../../../../services/new_job_capabilities/new_job_capabilities_service'; import { Description } from './description'; +import { Field } from '../../../../../../../../../common/types/fields'; import { MultiMetricJobCreator, + RareJobCreator, isMultiMetricJobCreator, - PopulationJobCreator, - isPopulationJobCreator, } from '../../../../../common/job_creator'; export const SplitFieldSelector: FC = () => { const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const jobCreator = jc as MultiMetricJobCreator | PopulationJobCreator; - const canClearSelection = isMultiMetricJobCreator(jc); + const jobCreator = jc as MultiMetricJobCreator | RareJobCreator; const runtimeCategoryFields = useMemo(() => filterCategoryFields(jobCreator.runtimeFields), []); - const categoryFields = useMemo( + const allCategoryFields = useMemo( () => [...newJobCapsService.categoryFields, ...runtimeCategoryFields], [] ); + const categoryFields = useFilteredCategoryFields( + allCategoryFields, + jobCreator, + jobCreatorUpdated + ); const [splitField, setSplitField] = useState(jobCreator.splitField); useEffect(() => { @@ -47,20 +51,39 @@ export const SplitFieldSelector: FC = () => { }, [jobCreatorUpdated]); return ( - + ); }; + +// remove the rare (by) and population (over) fields from the by field options in the rare wizard +function useFilteredCategoryFields( + allCategoryFields: Field[], + jobCreator: MultiMetricJobCreator | RareJobCreator, + jobCreatorUpdated: number +) { + const [fields, setFields] = useState(allCategoryFields); + + useEffect(() => { + if (isMultiMetricJobCreator(jobCreator)) { + setFields(allCategoryFields); + } else { + const rf = jobCreator.rareField; + const pf = jobCreator.populationField; + if (rf !== null || pf !== null) { + setFields(allCategoryFields.filter(({ name }) => name !== rf?.name && name !== pf?.name)); + } else { + setFields(allCategoryFields); + } + } + }, [jobCreatorUpdated]); + + return fields; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/index.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/index.ts new file mode 100644 index 0000000000000..216af994ed065 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { SplitFieldSelect } from './split_field_select'; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field_select.tsx rename to x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field_select/split_field_select.tsx diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx index 2d5f5e8a76637..2461cfc9d9d04 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx @@ -15,6 +15,7 @@ import { MultiMetricView } from './components/multi_metric_view'; import { PopulationView } from './components/population_view'; import { AdvancedView } from './components/advanced_view'; import { CategorizationView } from './components/categorization_view'; +import { RareView } from './components/rare_view'; import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout'; import { isSingleMetricJobCreator, @@ -22,34 +23,39 @@ import { isPopulationJobCreator, isCategorizationJobCreator, isAdvancedJobCreator, + isRareJobCreator, } from '../../../common/job_creator'; export const PickFieldsStep: FC = ({ setCurrentStep, isCurrentStep }) => { const { jobCreator, jobValidator, jobValidatorUpdated } = useContext(JobCreatorContext); const [nextActive, setNextActive] = useState(false); + const [selectionValid, setSelectionValid] = useState(false); useEffect(() => { - setNextActive(jobValidator.isPickFieldsStepValid); - }, [jobValidatorUpdated]); + setNextActive(selectionValid && jobValidator.isPickFieldsStepValid); + }, [jobValidatorUpdated, selectionValid]); return ( {isCurrentStep && ( {isSingleMetricJobCreator(jobCreator) && ( - + )} {isMultiMetricJobCreator(jobCreator) && ( - + )} {isPopulationJobCreator(jobCreator) && ( - + )} {isAdvancedJobCreator(jobCreator) && ( - + )} {isCategorizationJobCreator(jobCreator) && ( - + + )} + {isRareJobCreator(jobCreator) && ( + )} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx index c1c8c59496929..266c779e1e644 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx @@ -13,6 +13,7 @@ import { MultiMetricView } from '../../../pick_fields_step/components/multi_metr import { PopulationView } from '../../../pick_fields_step/components/population_view'; import { AdvancedView } from '../../../pick_fields_step/components/advanced_view'; import { CategorizationView } from '../../../pick_fields_step/components/categorization_view'; +import { RareView } from '../../../pick_fields_step/components/rare_view'; export const DetectorChart: FC = () => { const { jobCreator } = useContext(JobCreatorContext); @@ -24,6 +25,7 @@ export const DetectorChart: FC = () => { {jobCreator.type === JOB_TYPE.POPULATION && } {jobCreator.type === JOB_TYPE.ADVANCED && } {jobCreator.type === JOB_TYPE.CATEGORIZATION && } + {jobCreator.type === JOB_TYPE.RARE && } ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/job_details/job_details.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/job_details/job_details.tsx index 64f419be7979c..f6145ef812987 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/job_details/job_details.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/job_details/job_details.tsx @@ -111,10 +111,10 @@ export const JobDetails: FC = () => { defaultMessage: 'Population field', }), description: - isPopulationJobCreator(jobCreator) && jobCreator.splitField !== null ? ( - jobCreator.splitField.name + isPopulationJobCreator(jobCreator) && jobCreator.populationField !== null ? ( + jobCreator.populationField.name ) : ( - + { }), id: 'mlJobTypeLinkCategorizationJob', }, + { + onClick: () => navigateToPath(`/jobs/new_job/rare${getUrlParams()}`), + icon: { + type: RareIcon, + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.rareAriaLabel', { + defaultMessage: 'Rare job', + }), + }, + title: i18n.translate('xpack.ml.newJob.wizard.jobType.rareTitle', { + defaultMessage: 'Rare', + }), + description: i18n.translate('xpack.ml.newJob.wizard.jobType.rareDescription', { + defaultMessage: 'Detect rare values in time series data.', + }), + id: 'mlJobTypeLinkrareJob', + }, ]; return ( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/rare_job_icon.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/rare_job_icon.tsx new file mode 100644 index 0000000000000..f7ab7ff14d09b --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/rare_job_icon.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +export const RareIcon = ( + + + + +); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index 442bdba717f28..c5d9acce1ee93 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -24,6 +24,7 @@ import { jobCreatorFactory, isAdvancedJobCreator, isCategorizationJobCreator, + isRareJobCreator, } from '../../common/job_creator'; import { JOB_TYPE, @@ -171,6 +172,10 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); jobCreator.categorizationAnalyzer = anomalyDetectors.categorization_analyzer!; + } else if (isRareJobCreator(jobCreator)) { + const rare = newJobCapsService.getAggById('rare'); + const freqRare = newJobCapsService.getAggById('freq_rare'); + jobCreator.setDefaultDetectorProperties(rare, freqRare); } } diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index 726ec328d1cb2..56554e79fe95d 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -86,6 +86,16 @@ const getCategorizationBreadcrumbs = (navigateToPath: NavigateToPath, basePath: }, ]; +const getRareBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ + ...getBaseBreadcrumbs(navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.jobsBreadcrumbs.rareLabel', { + defaultMessage: 'Rare', + }), + href: '', + }, +]; + export const singleMetricRouteFactory = ( navigateToPath: NavigateToPath, basePath: string @@ -131,6 +141,12 @@ export const categorizationRouteFactory = ( breadcrumbs: getCategorizationBreadcrumbs(navigateToPath, basePath), }); +export const rareRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({ + path: '/jobs/new_job/rare', + render: (props, deps) => , + breadcrumbs: getRareBreadcrumbs(navigateToPath, basePath), +}); + const PageWrapper: FC = ({ location, jobType, deps }) => { const redirectToJobsManagementPage = useCreateAndNavigateToMlLink( ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE From 6e308a2a1e2c531557e6c2bcd1c85d62d18b10b9 Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Tue, 29 Jun 2021 13:28:40 +0300 Subject: [PATCH 53/74] [Migrations V2] Unskip migration_7.7.2_xpack_100k (#103435) --- .../migration_7.7.2_xpack_100k.test.ts | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts index cb7f5a000cefb..ea807beb83cea 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import Path from 'path'; -import Fs from 'fs'; -import Util from 'util'; +import path from 'path'; +import { unlink } from 'fs/promises'; import { REPO_ROOT } from '@kbn/dev-utils'; import { Env } from '@kbn/config'; import { getEnvOptions } from '@kbn/config/target/mocks'; @@ -18,16 +17,14 @@ import { InternalCoreStart } from '../../../internal_types'; import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; -const logFilePath = Path.join(__dirname, 'migration_test_kibana.log'); +const logFilePath = path.join(__dirname, 'migration_test_kibana.log'); -const asyncUnlink = Util.promisify(Fs.unlink); async function removeLogFile() { // ignore errors if it doesn't exist - await asyncUnlink(logFilePath).catch(() => void 0); + await unlink(logFilePath).catch(() => void 0); } -// FAILING on 7.13: https://github.com/elastic/kibana/issues/96895 -describe.skip('migration from 7.7.2-xpack with 100k objects', () => { +describe('migration from 7.7.2-xpack with 100k objects', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let coreStart: InternalCoreStart; @@ -64,12 +61,9 @@ describe.skip('migration from 7.7.2-xpack with 100k objects', () => { }, }, }, - loggers: [ - { - name: 'root', - appenders: ['file'], - }, - ], + root: { + appenders: ['default', 'file'], + }, }, }, { @@ -106,7 +100,7 @@ describe.skip('migration from 7.7.2-xpack with 100k objects', () => { await removeLogFile(); await startServers({ oss: false, - dataArchive: Path.join(__dirname, 'archives', '7.7.2_xpack_100k_obj.zip'), + dataArchive: path.join(__dirname, 'archives', '7.7.2_xpack_100k_obj.zip'), }); }); From 436ddef784f7ba960faca587fcefa2c7f0cd2f24 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Tue, 29 Jun 2021 04:43:58 -0600 Subject: [PATCH 54/74] [Maps] Only allow feature deletion when in delete-mode --- .../draw_control/draw_feature_control/draw_feature_control.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx index eb5ea9b5ddba5..9f2b99b9c41ab 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx @@ -85,7 +85,8 @@ export class DrawFeatureControl extends Component { _onClick = async (event: MapMouseEvent, drawControl?: MapboxDraw) => { const mbLngLatPoint: MbPoint = event.point; - if (!this.props.editLayer) { + // Currently feature deletion is the only onClick handling + if (!this.props.editLayer || this.props.drawShape !== DRAW_SHAPE.DELETE) { return; } const mbEditLayerIds = this.props.editLayer From 39ba74772896a24cebf7b9adb55aabd5b4dadddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 29 Jun 2021 13:01:36 +0200 Subject: [PATCH 55/74] [ILM] Add missing a11y tests (#102887) * [ILM] Added missing a11y tests * Fixed ILM functional tests, added after tests cleanup * Added review suggestions * Fixed section header renaming * Fixed section header renaming Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../policy_table/components/table_content.tsx | 2 + .../sections/policy_table/policy_table.tsx | 2 +- .../apps/index_lifecycle_management.ts | 130 +++++++++++++----- .../index_lifecycle_management/home_page.ts | 31 +++-- .../index_lifecycle_management_page.ts | 65 +++++---- 5 files changed, 155 insertions(+), 75 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx index 2c653ee5f76f6..ffc21ac4187fe 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -205,6 +205,7 @@ export const TableContent: React.FunctionComponent = ({ onClick: () => { setConfirmModal(renderAddPolicyToTemplateConfirmModal(policy)); }, + 'data-test-subj': 'addPolicyToTemplate', }); items.push({ name: deletePolicyLabel, @@ -214,6 +215,7 @@ export const TableContent: React.FunctionComponent = ({ onClick: () => { setConfirmModal(renderDeleteConfirmModal(policy)); }, + 'data-test-subj': 'deletePolicy', }); const panelTree = { id: 0, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx index 6ed7d42a694cc..30a2b9e68d69d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/policy_table.tsx @@ -152,7 +152,7 @@ export const PolicyTable: React.FunctionComponent = ({ + { + const actionsCell = await testSubjects.find(`policyTableCell-actions-${policyName}`); + const actionsButton = await actionsCell.findByTestSubject('policyActionsContextMenuButton'); + + await actionsButton.click(); + }; + describe('Index Lifecycle Management', async () => { before(async () => { - await esClient.ilm.putLifecycle({ policy: TEST_POLICY_NAME, body: TEST_POLICY_ALL_PHASES }); - await common.navigateToApp('indexLifecycleManagement'); + await esClient.ilm.putLifecycle({ policy: POLICY_NAME, body: POLICY_ALL_PHASES }); }); after(async () => { // @ts-expect-error @elastic/elasticsearch DeleteSnapshotLifecycleRequest.policy_id is required - await esClient.ilm.deleteLifecycle({ policy: TEST_POLICY_NAME }); + await esClient.ilm.deleteLifecycle({ policy: POLICY_NAME }); }); - it('Create Policy Form', async () => { - await retry.waitFor('Index Lifecycle Policy create/edit view to be present', async () => { - return testSubjects.isDisplayed('createPolicyButton'); + beforeEach(async () => { + await retry.waitFor('ILM app', async () => { + await common.navigateToApp('indexLifecycleManagement'); + return testSubjects.exists('ilmPageHeader'); }); + }); + + describe('Edit policy form', () => { + it('Create policy', async () => { + const createButtonTestSubject = 'createPolicyButton'; + await retry.waitFor('ILM create policy button', async () => { + return testSubjects.isDisplayed(createButtonTestSubject); + }); - // Navigate to create policy page and take snapshot - await testSubjects.click('createPolicyButton'); - await retry.waitFor('Index Lifecycle Policy create/edit view to be present', async () => { - return (await testSubjects.getVisibleText('policyTitle')) === 'Create policy'; + // Navigate to create policy page and take snapshot + await testSubjects.click(createButtonTestSubject); + await retry.waitFor('ILM create policy form', async () => { + return (await testSubjects.getVisibleText('policyTitle')) === 'Create policy'; + }); + + // Fill out form after enabling all phases and take snapshot. + await indexLifecycleManagement.fillNewPolicyForm({ + policyName: 'testPolicy', + warmEnabled: true, + coldEnabled: true, + frozenEnabled: true, + deleteEnabled: true, + }); + await a11y.testAppSnapshot(); }); - // Fill out form after enabling all phases and take snapshot. - await indexLifecycleManagement.fillNewPolicyForm('testPolicy', true, true, false); - await a11y.testAppSnapshot(); - }); + it('Edit policy', async () => { + const link = await findPolicyLinkInListView(POLICY_NAME); + await link.click(); + await retry.waitFor('ILM edit form', async () => { + return ( + (await testSubjects.getVisibleText('policyTitle')) === `Edit policy ${POLICY_NAME}` + ); + }); + await a11y.testAppSnapshot(); + }); - it('Send Request Flyout on New Policy Page', async () => { - // Take snapshot of the show request panel - await testSubjects.click('requestButton'); - await a11y.testAppSnapshot(); + it('Request flyout', async () => { + const link = await findPolicyLinkInListView(POLICY_NAME); + await link.click(); + await retry.waitFor('ILM request button', async () => { + return testSubjects.exists('requestButton'); + }); + // Take snapshot of the show request panel + await testSubjects.click('requestButton'); + await a11y.testAppSnapshot(); + }); + }); - // Close panel and save policy - await testSubjects.click('euiFlyoutCloseButton'); - await indexLifecycleManagement.saveNewPolicy(); + describe('Policies list', () => { + it('List policies view', async () => { + await a11y.testAppSnapshot(); + }); }); - it('List policies view', async () => { - await retry.waitFor('Index Lifecycle Policy create/edit view to be present', async () => { - await common.navigateToApp('indexLifecycleManagement'); - return testSubjects.exists('policyTablePolicyNameLink') ? true : false; + it('Add policy to index template modal', async () => { + await clickPolicyActionsButton(POLICY_NAME); + + const buttonSelector = 'addPolicyToTemplate'; + + await retry.waitFor('ILM add policy to index template button', async () => { + return testSubjects.isDisplayed(buttonSelector); + }); + await testSubjects.click(buttonSelector); + + await retry.waitFor('ILM add policy to index template modal to be present', async () => { + return testSubjects.isDisplayed('confirmModalTitleText'); }); + await a11y.testAppSnapshot(); }); - it('Edit policy with all phases view', async () => { - await retry.waitFor('Index Lifecycle Policy create/edit view to be present', async () => { - await common.navigateToApp('indexLifecycleManagement'); - return testSubjects.exists('policyTablePolicyNameLink'); + it('Delete policy modal', async () => { + await clickPolicyActionsButton(POLICY_NAME); + + const buttonSelector = 'deletePolicy'; + + await retry.waitFor('ILM delete policy button', async () => { + return testSubjects.isDisplayed(buttonSelector); }); - const link = await findPolicyLinkInListView(TEST_POLICY_NAME); - await link.click(); - await retry.waitFor('Index Lifecycle Policy create/edit view to be present', async () => { - return testSubjects.exists('policyTitle'); + await testSubjects.click(buttonSelector); + + await retry.waitFor('ILM delete policy modal to be present', async () => { + return testSubjects.isDisplayed('confirmModalTitleText'); }); + await a11y.testAppSnapshot(); }); }); diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index 50194552aec0a..bd70a50724a9c 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -8,37 +8,46 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +const policyName = 'testPolicy1'; + export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'indexLifecycleManagement']); const log = getService('log'); const retry = getService('retry'); - const testSubjects = getService('testSubjects'); + const esClient = getService('es'); describe('Home page', function () { before(async () => { await pageObjects.common.navigateToApp('indexLifecycleManagement'); }); + after(async () => { + // @ts-expect-error @elastic/elasticsearch DeleteSnapshotLifecycleRequest.policy_id is required + await esClient.ilm.deleteLifecycle({ policy: policyName }); + }); it('Loads the app', async () => { - await log.debug('Checking for section header'); - const headerText = await pageObjects.indexLifecycleManagement.sectionHeadingText(); + await log.debug('Checking for page header'); + const headerText = await pageObjects.indexLifecycleManagement.pageHeaderText(); expect(headerText).to.be('Index Lifecycle Policies'); const createPolicyButton = await pageObjects.indexLifecycleManagement.createPolicyButton(); expect(await createPolicyButton.isDisplayed()).to.be(true); }); - it('Create new policy with Warm and Cold Phases', async () => { - const policyName = 'testPolicy1'; - await pageObjects.indexLifecycleManagement.createNewPolicyAndSave( + it('Create new policy with all Phases', async () => { + await pageObjects.indexLifecycleManagement.createNewPolicyAndSave({ policyName, - true, - true, - false - ); + warmEnabled: true, + coldEnabled: true, + frozenEnabled: true, + deleteEnabled: true, + }); await retry.waitFor('navigation back to home page.', async () => { - return (await testSubjects.getVisibleText('sectionHeading')) === 'Index Lifecycle Policies'; + return ( + (await pageObjects.indexLifecycleManagement.pageHeaderText()) === + 'Index Lifecycle Policies' + ); }); await pageObjects.indexLifecycleManagement.increasePolicyListPageSize(); diff --git a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts index 2dd70f8a95717..0c913683a1628 100644 --- a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts +++ b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts @@ -7,28 +7,42 @@ import { map as mapAsync } from 'bluebird'; import { FtrProviderContext } from '../ftr_provider_context'; +interface Policy { + policyName: string; + warmEnabled?: boolean; + coldEnabled?: boolean; + frozenEnabled?: boolean; + deleteEnabled?: boolean; + snapshotRepository?: string; + minAges?: { [key: string]: { value: string; unit: string } }; +} + export function IndexLifecycleManagementPageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); return { - async sectionHeadingText() { - return await testSubjects.getVisibleText('sectionHeading'); + async pageHeaderText() { + return await testSubjects.getVisibleText('ilmPageHeader'); }, async createPolicyButton() { return await testSubjects.find('createPolicyButton'); }, - async fillNewPolicyForm( - policyName: string, - warmEnabled: boolean = false, - coldEnabled: boolean = false, - deletePhaseEnabled: boolean = false, - minAges: { [key: string]: { value: string; unit: string } } = { - warm: { value: '10', unit: 'd' }, - cold: { value: '15', unit: 'd' }, - frozen: { value: '20', unit: 'd' }, - } - ) { + async fillNewPolicyForm(policy: Policy) { + const { + policyName, + warmEnabled = false, + coldEnabled = false, + frozenEnabled = false, + deleteEnabled = false, + snapshotRepository = 'test', + minAges = { + warm: { value: '10', unit: 'd' }, + cold: { value: '15', unit: 'd' }, + frozen: { value: '20', unit: 'd' }, + }, + } = policy; + await testSubjects.setValue('policyNameField', policyName); if (warmEnabled) { await retry.try(async () => { @@ -42,7 +56,14 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider }); await testSubjects.setValue('cold-selectedMinimumAge', minAges.cold.value); } - if (deletePhaseEnabled) { + if (frozenEnabled) { + await retry.try(async () => { + await testSubjects.click('enablePhaseSwitch-frozen'); + }); + await testSubjects.setValue('frozen-selectedMinimumAge', minAges.frozen.value); + await testSubjects.setValue('searchableSnapshotCombobox', snapshotRepository); + } + if (deleteEnabled) { await retry.try(async () => { await testSubjects.click('enableDeletePhaseButton'); }); @@ -51,21 +72,9 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider async saveNewPolicy() { await testSubjects.click('savePolicyButton'); }, - async createNewPolicyAndSave( - policyName: string, - warmEnabled: boolean = false, - coldEnabled: boolean = false, - deletePhaseEnabled: boolean = false, - minAges?: { [key: string]: { value: string; unit: string } } - ) { + async createNewPolicyAndSave(policy: Policy) { await testSubjects.click('createPolicyButton'); - await this.fillNewPolicyForm( - policyName, - warmEnabled, - coldEnabled, - deletePhaseEnabled, - minAges - ); + await this.fillNewPolicyForm(policy); await this.saveNewPolicy(); }, From 0c8d5e8f89df135f42ab82079c07e02c7f171d49 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 29 Jun 2021 08:08:52 -0400 Subject: [PATCH 56/74] [Synthetics] Support synthetics dedupe strategy in Uptime app (#101678) * Add new runtime types for parsing on client/server. * Add more runtime types. * Remove dead code. * Mark parameter as unused. * Improve typing for failed journey request function. * Add new API functions, improve typing in a few others. * Modify API calls to work with new screenshot_ref data. * Fix untested refactor error. * Add required fields to runtime type. * Update typing in failed steps component. * Adapt client to work with old screenshots as well as new screenshot_ref. * Refactor composite code to reusable hook. * Implement screenshot blocks endpoint. * Define runtime types for full-size screenshots. * Delete dedicated screenshot and ref queries. * Optimize screenshot endpoint by combining queries. * Handle parsing error. * Clean up screenshot/ref typings. * Remove dead types. DRY a type out. * Simplify types. * Improve typing in step screenshot components. * Prefer PNG to JPG for canvas composite op. * Simplify and clean up some code. * Remove reliance on `Ping` type, clean up types. * Add a comment. * Add a comment. * Fix typing for `FailedStep` component. * Standardize loading spinner sizes. * Add comments to composite code. * Remove unnecessary optional chaining. * Reformat error string. * Remove unneeded key from request return object. * Add a comment to a return object explaining very large cache value. * Make type annotation more accurate. * Resolve some type and test errors. * Clean up remaining type errors. * Move type definitions to simplify imports. * Simplify `PingTimestamp` interface. * Refactor failing unit test to use RTL and actually test things. * Add tests for new helper functions. * Add a comment. * Test `PingTimestamp` for screenshot ref data. * Test `StepImageCaption` for ref data. * Improve typing for step list column definitions. * Harden a test. * Extract code to avoid repeated declarations. * Create centralized mock for `useCompositeImage`. * Add test for ref to `StepScreenshotDisplay`. * Add tests for `getJourneyDetails`. * Extract search results wrapper to helper lib. * Add tests for `getJourneyFailedSteps`. * Add support for aggs to result helper wrapper. * Write tests for `getJourneyScreenshot` and simplify type checking. * Write tests for `getJourneyScreenshotBlocks`. * Simplify prop types for `FailedStep`. * Remove unused type. * Fix regression in step navigating for new style screenshots. * Implement PR feedback. * Implement PR feedback. * Implement PR feedback. * Reduce limit of screenshot block queries from 10k to 1k. * Remove redundant field selection from ES query. * Implement PR feedback. * Fix regression that caused "Last successful step" to not show an image. * Delete unused props from `Ping` runtime type. * More precise naming. * Naming improvements. Add `useCallback` to prevent callback re-declaration. * Prefer explicit props to `{...spread}` syntax. * Remove redundant type checking. * Delete obsolete unit tests. * Fix a regression. * Add effect to `useEffect`. --- .../common/runtime_types/monitor/details.ts | 31 --- .../common/runtime_types/monitor/index.ts | 1 - .../uptime/common/runtime_types/ping/index.ts | 1 + .../uptime/common/runtime_types/ping/ping.ts | 58 ++--- .../runtime_types/ping/synthetics.test.ts | 150 ++++++++++++ .../common/runtime_types/ping/synthetics.ts | 207 ++++++++++++++++ .../monitor/ping_list/columns/failed_step.tsx | 10 +- .../ping_timestamp/nav_buttons.test.tsx | 89 ------- .../columns/ping_timestamp/nav_buttons.tsx | 59 ----- .../ping_timestamp/no_image_display.tsx | 2 +- .../ping_timestamp/ping_timestamp.test.tsx | 76 +++--- .../columns/ping_timestamp/ping_timestamp.tsx | 29 ++- .../step_image_caption.test.tsx | 9 + .../ping_timestamp/step_image_caption.tsx | 5 +- .../ping_timestamp/step_image_popover.tsx | 151 ++++++++++-- .../monitor/ping_list/ping_list.test.tsx | 6 +- .../monitor/ping_list/ping_list.tsx | 9 +- .../step_detail/use_monitor_breadcrumb.tsx | 10 +- .../step_expanded_row/screenshot_link.tsx | 4 +- .../step_expanded_row/step_screenshots.tsx | 21 +- .../synthetics/check_steps/step_image.tsx | 9 +- ...step_list.test.tsx => steps_list.test.tsx} | 16 +- .../synthetics/check_steps/steps_list.tsx | 40 +-- .../check_steps/use_expanded_row.test.tsx | 24 +- .../check_steps/use_expanded_row.tsx | 20 +- .../synthetics/console_event.test.tsx | 10 +- .../components/synthetics/console_event.tsx | 6 +- .../console_output_event_list.test.tsx | 228 +++++++----------- .../synthetics/console_output_event_list.tsx | 12 +- .../synthetics/executed_step.test.tsx | 12 +- .../components/synthetics/executed_step.tsx | 4 +- .../step_screenshot_display.test.tsx | 45 +++- .../synthetics/step_screenshot_display.tsx | 161 +++++++++---- x-pack/plugins/uptime/public/hooks/index.ts | 9 +- .../public/hooks/use_composite_image.ts | 55 +++++ .../lib/__mocks__/screenshot_ref.mock.ts | 62 +++++ .../lib/__mocks__/use_composite_image.mock.ts | 23 ++ .../lib/helper/compose_screenshot_images.ts | 56 +++++ .../uptime/public/state/api/journey.ts | 63 +++-- .../uptime/public/state/reducers/journey.ts | 4 +- .../lib/requests/get_journey_details.test.ts | 104 ++++++++ .../lib/requests/get_journey_details.ts | 10 +- .../requests/get_journey_failed_steps.test.ts | 90 +++++++ .../lib/requests/get_journey_failed_steps.ts | 21 +- .../requests/get_journey_screenshot.test.ts | 133 ++++++++++ .../lib/requests/get_journey_screenshot.ts | 51 ++-- .../get_journey_screenshot_blocks.test.ts | 56 +++++ .../requests/get_journey_screenshot_blocks.ts | 51 ++++ .../lib/requests/get_journey_steps.test.ts | 31 ++- .../server/lib/requests/get_journey_steps.ts | 73 ++++-- .../lib/requests/get_last_successful_step.ts | 8 +- .../uptime/server/lib/requests/helper.ts | 33 ++- .../uptime/server/lib/requests/index.ts | 2 + .../plugins/uptime/server/rest_api/index.ts | 2 + .../uptime/server/rest_api/pings/index.ts | 1 + .../pings/journey_screenshot_blocks.ts | 53 ++++ .../rest_api/pings/journey_screenshots.ts | 72 ++++-- .../uptime/server/rest_api/pings/journeys.ts | 61 ++--- .../synthetics/last_successful_step.ts | 32 ++- 59 files changed, 1936 insertions(+), 735 deletions(-) delete mode 100644 x-pack/plugins/uptime/common/runtime_types/monitor/details.ts create mode 100644 x-pack/plugins/uptime/common/runtime_types/ping/synthetics.test.ts create mode 100644 x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts delete mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx delete mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx rename x-pack/plugins/uptime/public/components/synthetics/check_steps/{step_list.test.tsx => steps_list.test.tsx} (94%) create mode 100644 x-pack/plugins/uptime/public/hooks/use_composite_image.ts create mode 100644 x-pack/plugins/uptime/public/lib/__mocks__/screenshot_ref.mock.ts create mode 100644 x-pack/plugins/uptime/public/lib/__mocks__/use_composite_image.mock.ts create mode 100644 x-pack/plugins/uptime/public/lib/helper/compose_screenshot_images.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_details.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.ts create mode 100644 x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor/details.ts b/x-pack/plugins/uptime/common/runtime_types/monitor/details.ts deleted file mode 100644 index 6910a3091b67f..0000000000000 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/details.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -// IO type for validation -export const PingErrorType = t.intersection([ - t.partial({ - code: t.string, - id: t.string, - stack_trace: t.string, - type: t.string, - }), - t.type({ - // this is _always_ on the error field - message: t.string, - }), -]); - -// Typescript type for type checking -export type PingError = t.TypeOf; - -export const MonitorDetailsType = t.intersection([ - t.type({ monitorId: t.string }), - t.partial({ error: PingErrorType, timestamp: t.string, alerts: t.unknown }), -]); -export type MonitorDetails = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor/index.ts b/x-pack/plugins/uptime/common/runtime_types/monitor/index.ts index b8c6b2910bd08..41daa9d2148ad 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -export * from './details'; export * from './locations'; export * from './state'; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/index.ts b/x-pack/plugins/uptime/common/runtime_types/ping/index.ts index 51addd76bee16..38ef0c7a835ad 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/index.ts @@ -7,3 +7,4 @@ export * from './histogram'; export * from './ping'; +export * from './synthetics'; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index 77b9473f2912e..d6875840a138c 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -7,7 +7,29 @@ import * as t from 'io-ts'; import { DateRangeType } from '../common'; -import { PingErrorType } from '../monitor'; + +// IO type for validation +export const PingErrorType = t.intersection([ + t.partial({ + code: t.string, + id: t.string, + stack_trace: t.string, + type: t.string, + }), + t.type({ + // this is _always_ on the error field + message: t.string, + }), +]); + +// Typescript type for type checking +export type PingError = t.TypeOf; + +export const MonitorDetailsType = t.intersection([ + t.type({ monitorId: t.string }), + t.partial({ error: PingErrorType, timestamp: t.string, alerts: t.unknown }), +]); +export type MonitorDetails = t.TypeOf; export const HttpResponseBodyType = t.partial({ bytes: t.number, @@ -193,10 +215,6 @@ export const PingType = t.intersection([ name: t.string, }), type: t.string, - // ui-related field - screenshotLoading: t.boolean, - // ui-related field - screenshotExists: t.boolean, blob: t.string, blob_mime: t.string, payload: t.partial({ @@ -242,36 +260,6 @@ export const PingType = t.intersection([ }), ]); -export const SyntheticsJourneyApiResponseType = t.intersection([ - t.type({ - checkGroup: t.string, - steps: t.array(PingType), - }), - t.partial({ - details: t.union([ - t.intersection([ - t.type({ - timestamp: t.string, - journey: PingType, - }), - t.partial({ - next: t.type({ - timestamp: t.string, - checkGroup: t.string, - }), - previous: t.type({ - timestamp: t.string, - checkGroup: t.string, - }), - }), - ]), - t.null, - ]), - }), -]); - -export type SyntheticsJourneyApiResponse = t.TypeOf; - export type Ping = t.TypeOf; // Convenience function for tests etc that makes an empty ping diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.test.ts b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.test.ts new file mode 100644 index 0000000000000..de92cfeb29e08 --- /dev/null +++ b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.test.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + isRefResult, + isFullScreenshot, + isScreenshotRef, + isScreenshotImageBlob, + RefResult, + FullScreenshot, + ScreenshotImageBlob, + ScreenshotRefImageData, +} from './synthetics'; + +describe('synthetics runtime types', () => { + let refResult: RefResult; + let fullScreenshot: FullScreenshot; + let screenshotImageBlob: ScreenshotImageBlob; + let screenshotRef: ScreenshotRefImageData; + + beforeEach(() => { + refResult = { + '@timestamp': '123', + monitor: { + check_group: 'check-group', + }, + screenshot_ref: { + width: 1200, + height: 900, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + height: 120, + width: 90, + }, + { + hash: 'hash2', + top: 0, + left: 90, + height: 120, + width: 90, + }, + ], + }, + synthetics: { + package_version: 'v1', + step: { + name: 'step name', + index: 0, + }, + type: 'step/screenshot_ref', + }, + }; + + fullScreenshot = { + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + step: { + name: 'step name', + }, + type: 'step/screenshot', + }, + }; + + screenshotImageBlob = { + stepName: null, + maxSteps: 1, + src: 'image data', + }; + + screenshotRef = { + stepName: null, + maxSteps: 1, + ref: { + screenshotRef: refResult, + blocks: [ + { + id: 'hash1', + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + }, + }, + { + id: 'hash2', + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + }, + }, + ], + }, + }; + }); + + describe('isRefResult', () => { + it('identifies refs correctly', () => { + expect(isRefResult(refResult)).toBe(true); + }); + + it('fails objects that do not correspond to the type', () => { + expect(isRefResult(fullScreenshot)).toBe(false); + expect(isRefResult(screenshotRef)).toBe(false); + expect(isRefResult(screenshotImageBlob)).toBe(false); + }); + }); + + describe('isScreenshot', () => { + it('identifies screenshot objects correctly', () => { + expect(isFullScreenshot(fullScreenshot)).toBe(true); + }); + + it('fails objects that do not correspond to the type', () => { + expect(isFullScreenshot(refResult)).toBe(false); + expect(isFullScreenshot(screenshotRef)).toBe(false); + expect(isFullScreenshot(screenshotImageBlob)).toBe(false); + }); + }); + + describe('isScreenshotImageBlob', () => { + it('identifies screenshot image blob objects correctly', () => { + expect(isScreenshotImageBlob(screenshotImageBlob)).toBe(true); + }); + + it('fails objects that do not correspond to the type', () => { + expect(isScreenshotImageBlob(refResult)).toBe(false); + expect(isScreenshotImageBlob(screenshotRef)).toBe(false); + expect(isScreenshotImageBlob(fullScreenshot)).toBe(false); + }); + }); + + describe('isScreenshotRef', () => { + it('identifies screenshot ref objects correctly', () => { + expect(isScreenshotRef(screenshotRef)).toBe(true); + }); + + it('fails objects that do not correspond to the type', () => { + expect(isScreenshotRef(refResult)).toBe(false); + expect(isScreenshotRef(fullScreenshot)).toBe(false); + expect(isScreenshotRef(screenshotImageBlob)).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts new file mode 100644 index 0000000000000..cd6be645c7a62 --- /dev/null +++ b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRight } from 'fp-ts/lib/Either'; +import * as t from 'io-ts'; + +/** + * This type has some overlap with the Ping type, but it helps avoid runtime type + * check failures and removes a lot of unnecessary fields that our Synthetics UI code + * does not care about. + */ +export const JourneyStepType = t.intersection([ + t.partial({ + monitor: t.partial({ + duration: t.type({ + us: t.number, + }), + name: t.string, + status: t.string, + type: t.string, + }), + synthetics: t.partial({ + error: t.partial({ + message: t.string, + stack: t.string, + }), + payload: t.partial({ + message: t.string, + source: t.string, + status: t.string, + text: t.string, + }), + step: t.type({ + index: t.number, + name: t.string, + }), + isFullScreenshot: t.boolean, + isScreenshotRef: t.boolean, + }), + }), + t.type({ + _id: t.string, + '@timestamp': t.string, + monitor: t.type({ + id: t.string, + check_group: t.string, + }), + synthetics: t.type({ + type: t.string, + }), + }), +]); + +export type JourneyStep = t.TypeOf; + +export const FailedStepsApiResponseType = t.type({ + checkGroups: t.array(t.string), + steps: t.array(JourneyStepType), +}); + +export type FailedStepsApiResponse = t.TypeOf; + +/** + * The individual screenshot blocks Synthetics uses to reduce disk footprint. + */ +export const ScreenshotBlockType = t.type({ + hash: t.string, + top: t.number, + left: t.number, + height: t.number, + width: t.number, +}); + +/** + * The old style of screenshot document that contains a full screenshot blob. + */ +export const FullScreenshotType = t.type({ + synthetics: t.intersection([ + t.partial({ + blob: t.string, + }), + t.type({ + blob: t.string, + blob_mime: t.string, + step: t.type({ + name: t.string, + }), + type: t.literal('step/screenshot'), + }), + ]), +}); + +export type FullScreenshot = t.TypeOf; + +export function isFullScreenshot(data: unknown): data is FullScreenshot { + return isRight(FullScreenshotType.decode(data)); +} + +/** + * The ref used by synthetics to organize the blocks needed to recompose a + * fragmented image. + */ +export const RefResultType = t.type({ + '@timestamp': t.string, + monitor: t.type({ + check_group: t.string, + }), + screenshot_ref: t.type({ + width: t.number, + height: t.number, + blocks: t.array(ScreenshotBlockType), + }), + synthetics: t.type({ + package_version: t.string, + step: t.type({ + name: t.string, + index: t.number, + }), + type: t.literal('step/screenshot_ref'), + }), +}); + +export type RefResult = t.TypeOf; + +export function isRefResult(data: unknown): data is RefResult { + return isRight(RefResultType.decode(data)); +} + +/** + * Represents the result of querying for the legacy-style full screenshot blob. + */ +export const ScreenshotImageBlobType = t.type({ + stepName: t.union([t.null, t.string]), + maxSteps: t.number, + src: t.string, +}); + +export type ScreenshotImageBlob = t.TypeOf; + +export function isScreenshotImageBlob(data: unknown): data is ScreenshotImageBlob { + return isRight(ScreenshotImageBlobType.decode(data)); +} + +/** + * Represents the block blobs stored by hash. These documents are used to recompose synthetics images. + */ +export const ScreenshotBlockDocType = t.type({ + id: t.string, + synthetics: t.type({ + blob: t.string, + blob_mime: t.string, + }), +}); + +export type ScreenshotBlockDoc = t.TypeOf; + +/** + * Contains the fields requried by the Synthetics UI when utilizing screenshot refs. + */ +export const ScreenshotRefImageDataType = t.type({ + stepName: t.union([t.null, t.string]), + maxSteps: t.number, + ref: t.type({ + screenshotRef: RefResultType, + blocks: t.array(ScreenshotBlockDocType), + }), +}); + +export type ScreenshotRefImageData = t.TypeOf; + +export function isScreenshotRef(data: unknown): data is ScreenshotRefImageData { + return isRight(ScreenshotRefImageDataType.decode(data)); +} + +export const SyntheticsJourneyApiResponseType = t.intersection([ + t.type({ + checkGroup: t.string, + steps: t.array(JourneyStepType), + }), + t.partial({ + details: t.union([ + t.intersection([ + t.type({ + timestamp: t.string, + journey: JourneyStepType, + }), + t.partial({ + next: t.type({ + timestamp: t.string, + checkGroup: t.string, + }), + previous: t.type({ + timestamp: t.string, + checkGroup: t.string, + }), + }), + ]), + t.null, + ]), + }), +]); + +export type SyntheticsJourneyApiResponse = t.TypeOf; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/failed_step.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/failed_step.tsx index 7de3a09c5c160..38f51a2bafebb 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/failed_step.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/failed_step.tsx @@ -6,16 +6,16 @@ */ import React from 'react'; -import { Ping, SyntheticsJourneyApiResponse } from '../../../../../common/runtime_types/ping'; +import { FailedStepsApiResponse } from '../../../../../common/runtime_types/ping/synthetics'; interface Props { - ping: Ping; - failedSteps?: SyntheticsJourneyApiResponse; + checkGroup?: string; + failedSteps?: FailedStepsApiResponse; } -export const FailedStep = ({ ping, failedSteps }: Props) => { +export const FailedStep = ({ checkGroup, failedSteps }: Props) => { const thisFailedStep = failedSteps?.steps?.find( - (fs) => fs.monitor.check_group === ping.monitor.check_group + (fs) => !!checkGroup && fs.monitor.check_group === checkGroup ); if (!thisFailedStep) { diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx deleted file mode 100644 index 30e46b1a3d638..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { fireEvent, waitFor } from '@testing-library/react'; -import React from 'react'; -import { render } from '../../../../../lib/helper/rtl_helpers'; -import { NavButtons, NavButtonsProps } from './nav_buttons'; - -describe('NavButtons', () => { - let defaultProps: NavButtonsProps; - - beforeEach(() => { - defaultProps = { - maxSteps: 3, - stepNumber: 2, - setStepNumber: jest.fn(), - setIsImagePopoverOpen: jest.fn(), - }; - }); - - it('labels prev and next buttons', () => { - const { getByLabelText } = render(); - - expect(getByLabelText('Previous step')); - expect(getByLabelText('Next step')); - }); - - it('increments step number on next click', async () => { - const { getByLabelText } = render(); - - const nextButton = getByLabelText('Next step'); - - fireEvent.click(nextButton); - - await waitFor(() => { - expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); - expect(defaultProps.setStepNumber).toHaveBeenCalledWith(3); - }); - }); - - it('decrements step number on prev click', async () => { - const { getByLabelText } = render(); - - const nextButton = getByLabelText('Previous step'); - - fireEvent.click(nextButton); - - await waitFor(() => { - expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); - expect(defaultProps.setStepNumber).toHaveBeenCalledWith(1); - }); - }); - - it('disables `next` button on final step', () => { - defaultProps.stepNumber = 3; - - const { getByLabelText } = render(); - - // getByLabelText('Next step'); - expect(getByLabelText('Next step')).toHaveAttribute('disabled'); - expect(getByLabelText('Previous step')).not.toHaveAttribute('disabled'); - }); - - it('disables `prev` button on final step', () => { - defaultProps.stepNumber = 1; - - const { getByLabelText } = render(); - - expect(getByLabelText('Next step')).not.toHaveAttribute('disabled'); - expect(getByLabelText('Previous step')).toHaveAttribute('disabled'); - }); - - it('opens popover when mouse enters', async () => { - const { getByLabelText } = render(); - - const nextButton = getByLabelText('Next step'); - - fireEvent.mouseEnter(nextButton); - - await waitFor(() => { - expect(defaultProps.setIsImagePopoverOpen).toHaveBeenCalledTimes(1); - expect(defaultProps.setIsImagePopoverOpen).toHaveBeenCalledWith(true); - }); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx deleted file mode 100644 index 3b0aad721be8a..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx +++ /dev/null @@ -1,59 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButtonIcon, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; -import React, { MouseEvent } from 'react'; -import { nextAriaLabel, prevAriaLabel } from './translations'; - -export interface NavButtonsProps { - maxSteps?: number; - setIsImagePopoverOpen: React.Dispatch>; - setStepNumber: React.Dispatch>; - stepNumber: number; -} - -export const NavButtons: React.FC = ({ - maxSteps, - setIsImagePopoverOpen, - setStepNumber, - stepNumber, -}) => ( - setIsImagePopoverOpen(true)} - style={{ position: 'absolute', bottom: 0, left: 30 }} - > - - ) => { - setStepNumber(stepNumber - 1); - evt.stopPropagation(); - }} - iconType="arrowLeft" - aria-label={prevAriaLabel} - /> - - - ) => { - setStepNumber(stepNumber + 1); - evt.stopPropagation(); - }} - iconType="arrowRight" - aria-label={nextAriaLabel} - /> - - -); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx index 827131d64f500..6c341e7cb25ac 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx @@ -27,7 +27,7 @@ export const NoImageDisplay: React.FC = ({ {isLoading || isPending ? ( ) : ( diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx index d628b2d8388f9..ed74b502add11 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx @@ -10,10 +10,11 @@ import { fireEvent, waitFor } from '@testing-library/react'; import { PingTimestamp } from './ping_timestamp'; import { mockReduxHooks } from '../../../../../lib/helper/test_helpers'; import { render } from '../../../../../lib/helper/rtl_helpers'; -import { Ping } from '../../../../../../common/runtime_types/ping'; import * as observabilityPublic from '../../../../../../../observability/public'; import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; import moment from 'moment'; +import '../../../../../lib/__mocks__/use_composite_image.mock'; +import { mockRef } from '../../../../../lib/__mocks__/screenshot_ref.mock'; mockReduxHooks(); @@ -27,40 +28,13 @@ jest.mock('../../../../../../../observability/public', () => { }); describe('Ping Timestamp component', () => { - let response: Ping; + let checkGroup: string; + let timestamp: string; const { FETCH_STATUS } = observabilityPublic; beforeAll(() => { - response = { - ecs: { version: '1.6.0' }, - agent: { - ephemeral_id: '52ce1110-464f-4d74-b94c-3c051bf12589', - id: '3ebcd3c2-f5c3-499e-8d86-80f98e5f4c08', - name: 'docker-desktop', - type: 'heartbeat', - version: '7.10.0', - hostname: 'docker-desktop', - }, - monitor: { - status: 'up', - check_group: 'f58a484f-2ffb-11eb-9b35-025000000001', - duration: { us: 1528598 }, - id: 'basic addition and completion of single task', - name: 'basic addition and completion of single task', - type: 'browser', - timespan: { lt: '2020-11-26T15:29:56.820Z', gte: '2020-11-26T15:28:56.820Z' }, - }, - url: { - full: 'file:///opt/elastic-synthetics/examples/todos/app/index.html', - scheme: 'file', - domain: '', - path: '/opt/elastic-synthetics/examples/todos/app/index.html', - }, - synthetics: { type: 'heartbeat/summary' }, - summary: { up: 1, down: 0 }, - timestamp: '2020-11-26T15:28:56.896Z', - docId: '0WErBXYB0mvWTKLO-yQm', - }; + checkGroup = 'f58a484f-2ffb-11eb-9b35-025000000001'; + timestamp = '2020-11-26T15:28:56.896Z'; }); it.each([[FETCH_STATUS.PENDING], [FETCH_STATUS.LOADING]])( @@ -70,7 +44,7 @@ describe('Ping Timestamp component', () => { .spyOn(observabilityPublic, 'useFetcher') .mockReturnValue({ status: fetchStatus, data: null, refetch: () => null }); const { getByTestId } = render( - + ); expect(getByTestId('pingTimestampSpinner')).toBeInTheDocument(); } @@ -81,7 +55,7 @@ describe('Ping Timestamp component', () => { .spyOn(observabilityPublic, 'useFetcher') .mockReturnValue({ status: FETCH_STATUS.SUCCESS, data: null, refetch: () => null }); const { getByTestId } = render( - + ); expect(getByTestId('pingTimestampNoImageAvailable')).toBeInTheDocument(); }); @@ -90,11 +64,11 @@ describe('Ping Timestamp component', () => { const src = 'http://sample.com/sampleImageSrc.png'; jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ status: FETCH_STATUS.SUCCESS, - data: { src }, + data: { maxSteps: 2, stepName: 'test', src }, refetch: () => null, }); const { container } = render( - + ); expect(container.querySelector('img')?.src).toBe(src); }); @@ -103,12 +77,37 @@ describe('Ping Timestamp component', () => { const src = 'http://sample.com/sampleImageSrc.png'; jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ status: FETCH_STATUS.SUCCESS, - data: { src }, + data: { maxSteps: 1, stepName: null, src }, refetch: () => null, }); const { getByAltText, getAllByText, queryByAltText } = render( - + ); + + const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption[0]); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption[0]); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + }); + + it('handles screenshot ref data', async () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: mockRef, + refetch: () => null, + }); + + const { getByAltText, getByText, getByRole, getAllByText, queryByAltText } = render( + + ); + + await waitFor(() => getByRole('img')); const caption = getAllByText('Nov 26, 2020 10:28:56 AM'); fireEvent.mouseEnter(caption[0]); @@ -119,5 +118,6 @@ describe('Ping Timestamp component', () => { fireEvent.mouseLeave(caption[0]); await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + expect(getByText('Step: 1 of 1')); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx index 16553e9de8604..8e2dc1b4c24e0 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx @@ -9,7 +9,11 @@ import React, { useContext, useEffect, useState } from 'react'; import useIntersection from 'react-use/lib/useIntersection'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Ping } from '../../../../../../common/runtime_types/ping'; +import { + isScreenshotImageBlob, + isScreenshotRef, + ScreenshotRefImageData, +} from '../../../../../../common/runtime_types/ping'; import { useFetcher, FETCH_STATUS } from '../../../../../../../observability/public'; import { getJourneyScreenshot } from '../../../../../state/api/journey'; import { UptimeSettingsContext } from '../../../../../contexts'; @@ -27,12 +31,12 @@ const StepDiv = styled.div` `; interface Props { + checkGroup?: string; label?: string; - ping: Ping; initialStepNo?: number; } -export const PingTimestamp = ({ label, ping, initialStepNo = 1 }: Props) => { +export const PingTimestamp = ({ label, checkGroup, initialStepNo = 1 }: Props) => { const [stepNumber, setStepNumber] = useState(initialStepNo); const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false); @@ -42,7 +46,7 @@ export const PingTimestamp = ({ label, ping, initialStepNo = 1 }: Props) => { const { basePath } = useContext(UptimeSettingsContext); - const imgPath = `${basePath}/api/uptime/journey/screenshot/${ping.monitor.check_group}/${stepNumber}`; + const imgPath = `${basePath}/api/uptime/journey/screenshot/${checkGroup}/${stepNumber}`; const intersection = useIntersection(intersectionRef, { root: null, @@ -55,13 +59,19 @@ export const PingTimestamp = ({ label, ping, initialStepNo = 1 }: Props) => { return getJourneyScreenshot(imgPath); }, [intersection?.intersectionRatio, stepNumber]); + const [screenshotRef, setScreenshotRef] = useState(undefined); useEffect(() => { - if (data) { + if (isScreenshotRef(data)) { + setScreenshotRef(data); + } else if (isScreenshotImageBlob(data)) { setStepImages((prevState) => [...prevState, data?.src]); } }, [data]); - const imgSrc = stepImages?.[stepNumber - 1] ?? data?.src; + let imgSrc; + if (isScreenshotImageBlob(data)) { + imgSrc = stepImages?.[stepNumber - 1] ?? data.src; + } const captionContent = formatCaptionContent(stepNumber, data?.maxSteps); @@ -71,6 +81,7 @@ export const PingTimestamp = ({ label, ping, initialStepNo = 1 }: Props) => { { onMouseLeave={() => setIsImagePopoverOpen(false)} ref={intersectionRef} > - {imgSrc ? ( + {(imgSrc || screenshotRef) && ( - ) : ( + )} + {!imgSrc && !screenshotRef && ( { let defaultProps: StepImageCaptionProps; @@ -91,4 +92,12 @@ describe('StepImageCaption', () => { getByText('test caption content'); }); + + it('renders caption content for screenshot ref data', async () => { + const { getByText } = render( + + ); + + getByText('test caption content'); + }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx index 80d41ccc23dc8..a2858348ed59c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx @@ -9,10 +9,12 @@ import React, { MouseEvent, useEffect } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { nextAriaLabel, prevAriaLabel } from './translations'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { ScreenshotRefImageData } from '../../../../../../common/runtime_types'; export interface StepImageCaptionProps { captionContent: string; imgSrc?: string; + imgRef?: ScreenshotRefImageData; maxSteps?: number; setStepNumber: React.Dispatch>; stepNumber: number; @@ -30,6 +32,7 @@ const ImageCaption = euiStyled.div` export const StepImageCaption: React.FC = ({ captionContent, + imgRef, imgSrc, maxSteps, setStepNumber, @@ -54,7 +57,7 @@ export const StepImageCaption: React.FC = ({ }} >
    - {imgSrc && ( + {(imgSrc || imgRef) && ( = ({ + captionContent, + imageCaption, + imageData, +}) => + imageData ? ( + + ) : ( + + ); + +/** + * This component provides an intermediate step for composite images. It causes a loading spinner to appear + * while the image is being re-assembled, then calls the default image component and provides a data URL for the image. + */ +const RecomposedScreenshotImage: React.FC< + ScreenshotImageProps & { + imgRef: ScreenshotRefImageData; + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ captionContent, imageCaption, imageData, imgRef, setImageData }) => { + // initially an undefined URL value is passed to the image display, and a loading spinner is rendered. + // `useCompositeImage` will call `setUrl` when the image is composited, and the updated `url` will display. + useCompositeImage(imgRef, setImageData, imageData); + + return ( + + ); +}; + export interface StepImagePopoverProps { captionContent: string; imageCaption: JSX.Element; - imgSrc: string; + imgSrc?: string; + imgRef?: ScreenshotRefImageData; isImagePopoverOpen: boolean; } +const StepImageComponent: React.FC< + Omit & { + setImageData: React.Dispatch; + imageData: string | undefined; + } +> = ({ captionContent, imageCaption, imageData, imgRef, imgSrc, setImageData }) => { + if (imgSrc) { + return ( + + ); + } else if (imgRef) { + return ( + + ); + } + return null; +}; + export const StepImagePopover: React.FC = ({ captionContent, imageCaption, + imgRef, imgSrc, isImagePopoverOpen, -}) => ( - +}) => { + const [imageData, setImageData] = React.useState(imgSrc || undefined); + + React.useEffect(() => { + // for legacy screenshots, when a new image arrives, we must overwrite it + if (imgSrc && imgSrc !== imageData) { + setImageData(imgSrc); } - isOpen={isImagePopoverOpen} - closePopover={() => {}} - > - - -); + }, [imgSrc, imageData]); + + const setImageDataCallback = React.useCallback( + (newImageData: string | undefined) => setImageData(newImageData), + [setImageData] + ); + return ( + + } + isOpen={isImagePopoverOpen} + closePopover={() => {}} + > + {imageData ? ( + + ) : ( + + )} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.test.tsx index bf5b0215e7d7a..c185303447854 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.test.tsx @@ -62,7 +62,7 @@ describe('PingList component', () => { ...response, error: undefined, loading: false, - failedSteps: { steps: [], checkGroup: '1-f-4d-4f' }, + failedSteps: { steps: [], checkGroups: ['1-f-4d-4f'] }, }); }); @@ -72,7 +72,7 @@ describe('PingList component', () => { total: 0, error: undefined, loading: true, - failedSteps: { steps: [], checkGroup: '1-f-4d-4f' }, + failedSteps: { steps: [], checkGroups: ['1-f-4d-4f'] }, }); const { getByText } = render(); expect(getByText('Loading history...')).toBeInTheDocument(); @@ -84,7 +84,7 @@ describe('PingList component', () => { total: 0, error: undefined, loading: false, - failedSteps: { steps: [], checkGroup: '1-f-4d-4f' }, + failedSteps: { steps: [], checkGroups: ['1-f-4d-4f'] }, }); const { getByText } = render(); expect(getByText('No history found')).toBeInTheDocument(); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx index 65644ce493906..b9ad176b8ed76 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list.tsx @@ -145,7 +145,10 @@ export const PingList = () => { field: 'timestamp', name: TIMESTAMP_LABEL, render: (timestamp: string, item: Ping) => ( - + ), }, ] @@ -185,8 +188,8 @@ export const PingList = () => { name: i18n.translate('xpack.uptime.pingList.columns.failedStep', { defaultMessage: 'Failed step', }), - render: (timestamp: string, item: Ping) => ( - + render: (_timestamp: string, item: Ping) => ( + ), }, ] diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumb.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumb.tsx index 8b85f05130d0b..f64d36fa0f789 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumb.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/use_monitor_breadcrumb.tsx @@ -10,13 +10,19 @@ import { i18n } from '@kbn/i18n'; import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { JourneyState } from '../../../../state/reducers/journey'; -import { Ping } from '../../../../../common/runtime_types/ping'; import { PLUGIN } from '../../../../../common/constants/plugin'; import { getShortTimeStamp } from '../../../overview/monitor_list/columns/monitor_status_column'; +interface ActiveStep { + monitor: { + id: string; + name?: string; + }; +} + interface Props { details: JourneyState['details']; - activeStep?: Ping; + activeStep?: ActiveStep; performanceBreakDownView?: boolean; } diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/screenshot_link.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/screenshot_link.tsx index 16068e0d72b46..1ba10da6ceace 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/screenshot_link.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/screenshot_link.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { ReactRouterEuiLink } from '../../../common/react_router_helpers'; -import { Ping } from '../../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../../common/runtime_types/ping/synthetics'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; const LabelLink = euiStyled.div` @@ -17,7 +17,7 @@ const LabelLink = euiStyled.div` `; interface Props { - lastSuccessfulStep: Ping; + lastSuccessfulStep: JourneyStep; } export const ScreenshotLink = ({ lastSuccessfulStep }: Props) => { diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx index eb7bc95751557..316154929320d 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { StepScreenshotDisplay } from '../../step_screenshot_display'; -import { Ping } from '../../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../../common/runtime_types/ping/synthetics'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { useFetcher } from '../../../../../../observability/public'; import { fetchLastSuccessfulStep } from '../../../../state/api/journey'; @@ -24,21 +24,22 @@ const Label = euiStyled.div` `; interface Props { - step: Ping; + step: JourneyStep; } export const StepScreenshots = ({ step }: Props) => { const isSucceeded = step.synthetics?.payload?.status === 'succeeded'; - const { data: lastSuccessfulStep } = useFetcher(() => { + const { data } = useFetcher(() => { if (!isSucceeded) { return fetchLastSuccessfulStep({ - timestamp: step.timestamp, + timestamp: step['@timestamp'], monitorId: step.monitor.id, stepIndex: step.synthetics?.step?.index!, }); } - }, [step.docId, step.timestamp]); + }, [step._id, step['@timestamp']]); + const lastSuccessfulStep: JourneyStep | undefined = data; return ( @@ -59,26 +60,28 @@ export const StepScreenshots = ({ step }: Props) => { - + {!isSucceeded && lastSuccessfulStep?.monitor && ( - + )} diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_image.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_image.tsx index 69a5ef91a5925..8fbc26ac25692 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_image.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_image.tsx @@ -7,18 +7,21 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { Ping } from '../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../common/runtime_types/ping/synthetics'; import { PingTimestamp } from '../../monitor/ping_list/columns/ping_timestamp'; interface Props { - step: Ping; + step: JourneyStep; } export const StepImage = ({ step }: Props) => { return ( - + {step.synthetics?.step?.name} diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_list.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.test.tsx similarity index 94% rename from x-pack/plugins/uptime/public/components/synthetics/check_steps/step_list.test.tsx rename to x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.test.tsx index 959bf0f644580..738960eb2af3e 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.test.tsx @@ -6,18 +6,18 @@ */ import React from 'react'; -import { Ping } from '../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../common/runtime_types/ping'; import { StepsList } from './steps_list'; import { render } from '../../../lib/helper/rtl_helpers'; describe('StepList component', () => { - let steps: Ping[]; + let steps: JourneyStep[]; beforeEach(() => { steps = [ { - docId: '1', - timestamp: '123', + _id: '1', + '@timestamp': '123', monitor: { id: 'MON_ID', duration: { @@ -39,8 +39,8 @@ describe('StepList component', () => { }, }, { - docId: '2', - timestamp: '124', + _id: '2', + '@timestamp': '124', monitor: { id: 'MON_ID', duration: { @@ -112,8 +112,8 @@ describe('StepList component', () => { it('uses appropriate count when non-step/end steps are included', () => { steps[0].synthetics!.payload!.status = 'succeeded'; steps.push({ - docId: '3', - timestamp: '125', + _id: '3', + '@timestamp': '125', monitor: { id: 'MON_ID', duration: { diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx index 47bf3ae0a1784..47b89e82dc5c7 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx @@ -5,11 +5,17 @@ * 2.0. */ -import { EuiBasicTable, EuiButtonIcon, EuiPanel, EuiTitle } from '@elastic/eui'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonIcon, + EuiPanel, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; import styled from 'styled-components'; -import { Ping } from '../../../../common/runtime_types'; +import { JourneyStep } from '../../../../common/runtime_types'; import { STATUS_LABEL } from '../../monitor/ping_list/translations'; import { COLLAPSE_LABEL, EXPAND_LABEL, STEP_NAME_LABEL } from '../translations'; import { StatusBadge } from '../status_badge'; @@ -23,7 +29,7 @@ export const SpanWithMargin = styled.span` `; interface Props { - data: Ping[]; + data: JourneyStep[]; error?: Error; loading: boolean; } @@ -34,7 +40,7 @@ interface StepStatusCount { succeeded: number; } -function isStepEnd(step: Ping) { +function isStepEnd(step: JourneyStep) { return step.synthetics?.type === 'step/end'; } @@ -62,7 +68,7 @@ function statusMessage(count: StepStatusCount, loading?: boolean) { }); } -function reduceStepStatus(prev: StepStatusCount, cur: Ping): StepStatusCount { +function reduceStepStatus(prev: StepStatusCount, cur: JourneyStep): StepStatusCount { if (cur.synthetics?.payload?.status === 'succeeded') { prev.succeeded += 1; return prev; @@ -75,15 +81,15 @@ function reduceStepStatus(prev: StepStatusCount, cur: Ping): StepStatusCount { } export const StepsList = ({ data, error, loading }: Props) => { - const steps = data.filter(isStepEnd); + const steps: JourneyStep[] = data.filter(isStepEnd); - const { expandedRows, toggleExpand } = useExpandedRow({ steps, allPings: data, loading }); + const { expandedRows, toggleExpand } = useExpandedRow({ steps, allSteps: data, loading }); - const columns: any[] = [ + const columns: Array> = [ { field: 'synthetics.payload.status', name: STATUS_LABEL, - render: (pingStatus: string, item: Ping) => ( + render: (pingStatus: string, item) => ( ), }, @@ -91,13 +97,13 @@ export const StepsList = ({ data, error, loading }: Props) => { align: 'left', field: 'timestamp', name: STEP_NAME_LABEL, - render: (timestamp: string, item: Ping) => , + render: (_timestamp: string, item) => , }, { align: 'left', field: 'timestamp', name: '', - render: (val: string, item: Ping) => ( + render: (_val: string, item) => ( { align: 'right', width: '24px', isExpander: true, - render: (ping: Ping) => { + render: (journeyStep: JourneyStep) => { return ( toggleExpand({ ping })} - aria-label={expandedRows[ping.docId] ? COLLAPSE_LABEL : EXPAND_LABEL} - iconType={expandedRows[ping.docId] ? 'arrowUp' : 'arrowDown'} + onClick={() => toggleExpand({ journeyStep })} + aria-label={expandedRows[journeyStep._id] ? COLLAPSE_LABEL : EXPAND_LABEL} + iconType={expandedRows[journeyStep._id] ? 'arrowUp' : 'arrowDown'} /> ); }, }, ]; - const getRowProps = (item: Ping) => { + const getRowProps = (item: JourneyStep) => { const { monitor } = item; return { @@ -134,7 +140,7 @@ export const StepsList = ({ data, error, loading }: Props) => { // we dont want to capture image click event if (targetElem.tagName !== 'IMG' && targetElem.tagName !== 'BUTTON') { - toggleExpand({ ping: item }); + toggleExpand({ journeyStep: item }); } }, }; diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx index d94122a7311ca..a195001903f6c 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx @@ -13,7 +13,7 @@ import { createMemoryHistory } from 'history'; import { useExpandedRow } from './use_expanded_row'; import { render } from '../../../lib/helper/rtl_helpers'; -import { Ping } from '../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../common/runtime_types'; import { SYNTHETIC_CHECK_STEPS_ROUTE } from '../../../../common/constants'; import { COLLAPSE_LABEL, EXPAND_LABEL } from '../translations'; import { act } from 'react-dom/test-utils'; @@ -25,17 +25,16 @@ describe('useExpandedROw', () => { const history = createMemoryHistory({ initialEntries: ['/journey/fake-group/steps'], }); - const steps: Ping[] = [ + const steps: JourneyStep[] = [ { - docId: '1', - timestamp: '123', + _id: '1', + '@timestamp': '123', monitor: { id: 'MON_ID', duration: { us: 10, }, status: 'down', - type: 'browser', check_group: 'fake-group', }, synthetics: { @@ -50,15 +49,14 @@ describe('useExpandedROw', () => { }, }, { - docId: '2', - timestamp: '124', + _id: '2', + '@timestamp': '124', monitor: { id: 'MON_ID', duration: { us: 10, }, status: 'down', - type: 'browser', check_group: 'fake-group', }, synthetics: { @@ -77,7 +75,7 @@ describe('useExpandedROw', () => { const Component = () => { const { expandedRows, toggleExpand } = useExpandedRow({ steps, - allPings: steps, + allSteps: steps, loading: false, }); @@ -86,13 +84,13 @@ describe('useExpandedROw', () => { return ( Step list - {steps.map((ping, index) => ( + {steps.map((journeyStep, index) => ( toggleExpand({ ping })} - aria-label={expandedRows[ping.docId] ? COLLAPSE_LABEL : EXPAND_LABEL} - iconType={expandedRows[ping.docId] ? 'arrowUp' : 'arrowDown'} + onClick={() => toggleExpand({ journeyStep })} + aria-label={expandedRows[journeyStep._id] ? COLLAPSE_LABEL : EXPAND_LABEL} + iconType={expandedRows[journeyStep._id] ? 'arrowUp' : 'arrowDown'} /> ))} diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.tsx index bb56b237dfbd2..24ef0d7fb7e8f 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.tsx @@ -8,17 +8,17 @@ import React, { useEffect, useState, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { ExecutedStep } from '../executed_step'; -import { Ping } from '../../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../../common/runtime_types/ping'; interface HookProps { loading: boolean; - allPings: Ping[]; - steps: Ping[]; + allSteps: JourneyStep[]; + steps: JourneyStep[]; } type ExpandRowType = Record; -export const useExpandedRow = ({ loading, steps, allPings }: HookProps) => { +export const useExpandedRow = ({ loading, steps, allSteps }: HookProps) => { const [expandedRows, setExpandedRows] = useState({}); // eui table uses index from 0, synthetics uses 1 @@ -26,13 +26,13 @@ export const useExpandedRow = ({ loading, steps, allPings }: HookProps) => { const getBrowserConsole = useCallback( (index: number) => { - return allPings.find( + return allSteps.find( (stepF) => stepF.synthetics?.type === 'journey/browserconsole' && stepF.synthetics?.step?.index! === index )?.synthetics?.payload?.text; }, - [allPings] + [allSteps] ); useEffect(() => { @@ -60,9 +60,9 @@ export const useExpandedRow = ({ loading, steps, allPings }: HookProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [checkGroupId, loading]); - const toggleExpand = ({ ping }: { ping: Ping }) => { + const toggleExpand = ({ journeyStep }: { journeyStep: JourneyStep }) => { // eui table uses index from 0, synthetics uses 1 - const stepIndex = ping.synthetics?.step?.index! - 1; + const stepIndex = journeyStep.synthetics?.step?.index! - 1; // If already expanded, collapse if (expandedRows[stepIndex]) { @@ -74,9 +74,9 @@ export const useExpandedRow = ({ loading, steps, allPings }: HookProps) => { ...expandedRows, [stepIndex]: ( ), diff --git a/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx index 35cad2b8d6b9b..b80613dbfece5 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx @@ -15,9 +15,10 @@ describe('ConsoleEvent component', () => { shallowWithIntl( { shallowWithIntl( = ({ event }) => { @@ -28,7 +28,7 @@ export const ConsoleEvent: FC = ({ event }) => { return ( - {event.timestamp} + {event['@timestamp']} {event.synthetics?.type} diff --git a/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.test.tsx index 3821748d4e92b..d35e526208ae9 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.test.tsx @@ -5,147 +5,101 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; import React from 'react'; +import { JourneyStep } from '../../../common/runtime_types/ping/synthetics'; +import { render } from '../../lib/helper/rtl_helpers'; import { ConsoleOutputEventList } from './console_output_event_list'; describe('ConsoleOutputEventList component', () => { + let steps: JourneyStep[]; + + beforeEach(() => { + steps = [ + { + '@timestamp': '123', + _id: '1', + monitor: { + check_group: 'check_group', + id: 'MON_ID', + duration: { + us: 10, + }, + status: 'down', + type: 'browser', + }, + synthetics: { + type: 'stderr', + }, + }, + { + '@timestamp': '124', + _id: '2', + monitor: { + check_group: 'check_group', + id: 'MON_ID', + duration: { + us: 10, + }, + status: 'down', + type: 'browser', + }, + synthetics: { + type: 'cmd/status', + }, + }, + { + '@timestamp': '124', + _id: '2', + monitor: { + check_group: 'check_group', + id: 'MON_ID', + duration: { + us: 10, + }, + status: 'down', + type: 'browser', + }, + synthetics: { + type: 'step/end', + }, + }, + { + '@timestamp': '125', + _id: '3', + monitor: { + check_group: 'check_group', + id: 'MON_ID', + duration: { + us: 10, + }, + status: 'down', + type: 'browser', + }, + synthetics: { + type: 'stdout', + }, + }, + ]; + }); + it('renders a component per console event', () => { - expect( - shallowWithIntl( - - ).find('EuiCodeBlock') - ).toMatchInlineSnapshot(` - - - - - - `); + const { getByRole, getByText, queryByText } = render( + + ); + expect(getByRole('heading').innerHTML).toBe('No steps ran'); + steps + .filter((step) => step.synthetics.type !== 'step/end') + .forEach((step) => { + expect(getByText(step['@timestamp'])); + expect(getByText(step.synthetics.type)); + }); + expect(queryByText('step/end')).toBeNull(); }); }); diff --git a/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.tsx b/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.tsx index df4314e5ccf1c..c34344717e3b4 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/console_output_event_list.tsx @@ -9,17 +9,17 @@ import { EuiCodeBlock, EuiSpacer, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC } from 'react'; import { ConsoleEvent } from './console_event'; -import { Ping } from '../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../common/runtime_types/ping'; import { JourneyState } from '../../state/reducers/journey'; interface Props { journey: JourneyState; } -const isConsoleStep = (step: Ping) => - step.synthetics?.type === 'stderr' || - step.synthetics?.type === 'stdout' || - step.synthetics?.type === 'cmd/status'; +const CONSOLE_STEP_TYPES = ['stderr', 'stdout', 'cmd/status']; + +const isConsoleStep = (step: JourneyStep) => + CONSOLE_STEP_TYPES.some((type) => type === step.synthetics.type); export const ConsoleOutputEventList: FC = ({ journey }) => (
    @@ -41,7 +41,7 @@ export const ConsoleOutputEventList: FC = ({ journey }) => ( {journey.steps.filter(isConsoleStep).map((consoleEvent) => ( - + ))}
    diff --git a/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx index 24b52e09adbf9..04fcf382fd861 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx @@ -8,15 +8,16 @@ import React from 'react'; import { ExecutedStep } from './executed_step'; import { render } from '../../lib/helper/rtl_helpers'; -import { Ping } from '../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../common/runtime_types/ping'; describe('ExecutedStep', () => { - let step: Ping; + let step: JourneyStep; beforeEach(() => { step = { - docId: 'docID', + _id: 'docID', monitor: { + check_group: 'check_group', duration: { us: 123, }, @@ -29,8 +30,9 @@ describe('ExecutedStep', () => { index: 4, name: 'STEP_NAME', }, + type: 'step/end', }, - timestamp: 'timestamp', + '@timestamp': 'timestamp', }; }); @@ -43,6 +45,7 @@ describe('ExecutedStep', () => { index: 3, name: 'STEP_NAME', }, + type: 'step/end', }; const { getByText } = render(); @@ -57,6 +60,7 @@ describe('ExecutedStep', () => { message: 'There was an error executing the step.', stack: 'some.stack.trace.string', }, + type: 'an error type', }; const { getByText } = render(); diff --git a/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx b/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx index a77b3dfe3ba21..c3016864c72a7 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx @@ -9,14 +9,14 @@ import { EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { CodeBlockAccordion } from './code_block_accordion'; -import { Ping } from '../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../common/runtime_types/ping'; import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; import { StepScreenshots } from './check_steps/step_expanded_row/step_screenshots'; const CODE_BLOCK_OVERFLOW_HEIGHT = 360; interface ExecutedStepProps { - step: Ping; + step: JourneyStep; index: number; loading: boolean; browserConsole?: string; diff --git a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx index 52d2eacaf0e52..8d35df51c2421 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.test.tsx @@ -8,6 +8,18 @@ import React from 'react'; import { StepScreenshotDisplay } from './step_screenshot_display'; import { render } from '../../lib/helper/rtl_helpers'; +import * as observabilityPublic from '../../../../observability/public'; +import '../../lib/__mocks__/use_composite_image.mock'; +import { mockRef } from '../../lib/__mocks__/screenshot_ref.mock'; + +jest.mock('../../../../observability/public', () => { + const originalModule = jest.requireActual('../../../../observability/public'); + + return { + ...originalModule, + useFetcher: jest.fn().mockReturnValue({ data: null, status: 'success' }), + }; +}); jest.mock('react-use/lib/useIntersection', () => () => ({ isIntersecting: true, @@ -18,7 +30,8 @@ describe('StepScreenshotDisplayProps', () => { const { getByAltText } = render( @@ -29,7 +42,12 @@ describe('StepScreenshotDisplayProps', () => { it('uses alternative text when step name not available', () => { const { getByAltText } = render( - + ); expect(getByAltText('Screenshot')).toBeInTheDocument(); @@ -39,11 +57,32 @@ describe('StepScreenshotDisplayProps', () => { const { getByTestId } = render( ); expect(getByTestId('stepScreenshotImageUnavailable')).toBeInTheDocument(); }); + + it('displays screenshot thumbnail for ref', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: observabilityPublic.FETCH_STATUS.SUCCESS, + data: { ...mockRef }, + refetch: () => null, + }); + + const { getByAltText } = render( + + ); + + expect(getByAltText('Screenshot for step with name "STEP_NAME"')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.tsx b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.tsx index 78c65b7d40803..1224a31cfabb4 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/step_screenshot_display.tsx @@ -5,16 +5,31 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiImage, EuiText } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiImage, + EuiLoadingSpinner, + EuiText, +} from '@elastic/eui'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useContext, useEffect, useRef, useState, FC } from 'react'; import useIntersection from 'react-use/lib/useIntersection'; +import { + isScreenshotRef as isAScreenshotRef, + ScreenshotRefImageData, +} from '../../../common/runtime_types'; import { UptimeSettingsContext, UptimeThemeContext } from '../../contexts'; +import { useFetcher } from '../../../../observability/public'; +import { getJourneyScreenshot } from '../../state/api/journey'; +import { useCompositeImage } from '../../hooks'; interface StepScreenshotDisplayProps { - screenshotExists?: boolean; + isScreenshotBlob: boolean; + isScreenshotRef: boolean; checkGroup?: string; stepIndex?: number; stepName?: string; @@ -36,9 +51,56 @@ const StepImage = styled(EuiImage)` } `; +const BaseStepImage = ({ + stepIndex, + stepName, + url, +}: Pick & { url?: string }) => { + if (!url) return ; + return ( + + ); +}; + +type ComposedStepImageProps = Pick & { + url: string | undefined; + imgRef: ScreenshotRefImageData; + setUrl: React.Dispatch; +}; + +const ComposedStepImage = ({ + stepIndex, + stepName, + url, + imgRef, + setUrl, +}: ComposedStepImageProps) => { + useCompositeImage(imgRef, setUrl, url); + if (!url) return ; + return ; +}; + export const StepScreenshotDisplay: FC = ({ checkGroup, - screenshotExists, + isScreenshotBlob: isScreenshotBlob, + isScreenshotRef, stepIndex, stepName, lazyLoad = true, @@ -64,60 +126,59 @@ export const StepScreenshotDisplay: FC = ({ } }, [hasIntersected, isIntersecting, setHasIntersected]); - let content: JSX.Element | null = null; const imgSrc = basePath + `/api/uptime/journey/screenshot/${checkGroup}/${stepIndex}`; - if ((hasIntersected || !lazyLoad) && screenshotExists) { - content = ( - - ); - } else if (screenshotExists === false) { - content = ( - - - - - - - - - - - - - ); - } + // When loading a legacy screenshot, set `url` to full-size screenshot path. + // Otherwise, we first need to composite the image. + const [url, setUrl] = useState(isScreenshotBlob ? imgSrc : undefined); + + // when the image is a composite, we need to fetch the data since we cannot specify a blob URL + const { data: screenshotRef } = useFetcher(() => { + if (isScreenshotRef) { + return getJourneyScreenshot(imgSrc); + } + }, [basePath, checkGroup, stepIndex, isScreenshotRef]); + + const shouldRenderImage = hasIntersected || !lazyLoad; return (
    - {content} + {shouldRenderImage && isScreenshotBlob && ( + + )} + {shouldRenderImage && isScreenshotRef && isAScreenshotRef(screenshotRef) && ( + + )} + {!isScreenshotBlob && !isScreenshotRef && ( + + + + + + + + + + + + + )}
    ); }; diff --git a/x-pack/plugins/uptime/public/hooks/index.ts b/x-pack/plugins/uptime/public/hooks/index.ts index 2850af51bb1ef..06b525a94600f 100644 --- a/x-pack/plugins/uptime/public/hooks/index.ts +++ b/x-pack/plugins/uptime/public/hooks/index.ts @@ -5,9 +5,10 @@ * 2.0. */ -export * from './use_monitor'; -export * from './use_url_params'; -export * from './use_telemetry'; +export * from './use_composite_image'; export * from './update_kuery_string'; -export * from './use_cert_status'; +export * from './use_monitor'; export * from './use_search_text'; +export * from './use_cert_status'; +export * from './use_telemetry'; +export * from './use_url_params'; diff --git a/x-pack/plugins/uptime/public/hooks/use_composite_image.ts b/x-pack/plugins/uptime/public/hooks/use_composite_image.ts new file mode 100644 index 0000000000000..6db3d05b8c968 --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_composite_image.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { composeScreenshotRef } from '../lib/helper/compose_screenshot_images'; +import { ScreenshotRefImageData } from '../../common/runtime_types/ping/synthetics'; + +/** + * Checks if two refs are the same. If the ref is unchanged, there's no need + * to run the expensive draw procedure. + */ +function isNewRef(a: ScreenshotRefImageData, b: ScreenshotRefImageData): boolean { + if (typeof a === 'undefined' || typeof b === 'undefined') return false; + const stepA = a.ref.screenshotRef.synthetics.step; + const stepB = b.ref.screenshotRef.synthetics.step; + return stepA.index !== stepB.index || stepA.name !== stepB.name; +} + +/** + * Assembles the data for a composite image and returns the composite to a callback. + * @param imgRef the data and dimensions for the composite image. + * @param onComposeImageSuccess sends the composited image to this callback. + * @param imageData this is the composited image value, if it is truthy the function will skip the compositing process + */ +export const useCompositeImage = ( + imgRef: ScreenshotRefImageData, + onComposeImageSuccess: React.Dispatch, + imageData?: string +): void => { + const [curRef, setCurRef] = React.useState(imgRef); + + React.useEffect(() => { + const canvas = document.createElement('canvas'); + + async function compose() { + await composeScreenshotRef(imgRef, canvas); + const imgData = canvas.toDataURL('image/png', 1.0); + onComposeImageSuccess(imgData); + } + + // if the URL is truthy it means it's already been composed, so there + // is no need to call the function + if (typeof imageData === 'undefined' || isNewRef(imgRef, curRef)) { + compose(); + setCurRef(imgRef); + } + return () => { + canvas.parentElement?.removeChild(canvas); + }; + }, [imgRef, onComposeImageSuccess, curRef, imageData]); +}; diff --git a/x-pack/plugins/uptime/public/lib/__mocks__/screenshot_ref.mock.ts b/x-pack/plugins/uptime/public/lib/__mocks__/screenshot_ref.mock.ts new file mode 100644 index 0000000000000..d3a005d982168 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/__mocks__/screenshot_ref.mock.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../common/runtime_types'; + +export const mockRef: ScreenshotRefImageData = { + maxSteps: 1, + stepName: 'load homepage', + ref: { + screenshotRef: { + '@timestamp': '2021-06-08T19:42:30.257Z', + synthetics: { + package_version: '1.0.0-beta.2', + step: { name: 'load homepage', index: 1 }, + type: 'step/screenshot_ref', + }, + screenshot_ref: { + blocks: [ + { + top: 0, + left: 0, + width: 160, + hash: 'd518801fc523cf02727cd520f556c4113b3098c7', + height: 90, + }, + { + top: 0, + left: 160, + width: 160, + hash: 'fa90345d5d7b05b1601e9ee645e663bc358869e0', + height: 90, + }, + ], + width: 1280, + height: 720, + }, + monitor: { check_group: 'a567cc7a-c891-11eb-bdf9-3e22fb19bf97' }, + }, + blocks: [ + { + id: 'd518801fc523cf02727cd520f556c4113b3098c7', + synthetics: { + blob: + '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', + blob_mime: 'image/jpeg', + }, + }, + { + id: 'fa90345d5d7b05b1601e9ee645e663bc358869e0', + synthetics: { + blob: + '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', + blob_mime: 'image/jpeg', + }, + }, + ], + }, +}; diff --git a/x-pack/plugins/uptime/public/lib/__mocks__/use_composite_image.mock.ts b/x-pack/plugins/uptime/public/lib/__mocks__/use_composite_image.mock.ts new file mode 100644 index 0000000000000..c4ab83ae6ee5d --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/__mocks__/use_composite_image.mock.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../common/runtime_types/ping/synthetics'; +import * as composeScreenshotImages from '../../hooks/use_composite_image'; + +jest + .spyOn(composeScreenshotImages, 'useCompositeImage') + .mockImplementation( + ( + _imgRef: ScreenshotRefImageData, + callback: React.Dispatch, + url?: string + ) => { + if (!url) { + callback('img src'); + } + } + ); diff --git a/x-pack/plugins/uptime/public/lib/helper/compose_screenshot_images.ts b/x-pack/plugins/uptime/public/lib/helper/compose_screenshot_images.ts new file mode 100644 index 0000000000000..7481a517d3c9e --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/helper/compose_screenshot_images.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotRefImageData } from '../../../common/runtime_types'; + +/** + * Draws image fragments on a canvas. + * @param data Contains overall image size, fragment dimensions, and the blobs of image data to render. + * @param canvas A canvas to use for the rendering. + * @returns A promise that will resolve when the final draw operation completes. + */ +export async function composeScreenshotRef( + data: ScreenshotRefImageData, + canvas: HTMLCanvasElement +) { + const { + ref: { screenshotRef, blocks }, + } = data; + + canvas.width = screenshotRef.screenshot_ref.width; + canvas.height = screenshotRef.screenshot_ref.height; + + const ctx = canvas.getContext('2d', { alpha: false }); + + /** + * We need to treat each operation as an async task, otherwise we will race between drawing image + * chunks and extracting the final data URL from the canvas; without this, the image could be blank or incomplete. + */ + const drawOperations: Array> = []; + + for (const block of screenshotRef.screenshot_ref.blocks) { + drawOperations.push( + new Promise((resolve, reject) => { + const img = new Image(); + const { top, left, width, height, hash } = block; + const blob = blocks.find((b) => b.id === hash); + if (!blob) { + reject(Error(`Error processing image. Expected image data with hash ${hash} is missing`)); + } else { + img.onload = () => { + ctx?.drawImage(img, left, top, width, height); + resolve(); + }; + img.src = `data:image/jpg;base64,${blob.synthetics.blob}`; + } + }) + ); + } + + // once all `draw` operations finish, caller can extract img string + return Promise.all(drawOperations); +} diff --git a/x-pack/plugins/uptime/public/state/api/journey.ts b/x-pack/plugins/uptime/public/state/api/journey.ts index 63796a66d1c5c..4e71a07c70b68 100644 --- a/x-pack/plugins/uptime/public/state/api/journey.ts +++ b/x-pack/plugins/uptime/public/state/api/journey.ts @@ -8,7 +8,12 @@ import { apiService } from './utils'; import { FetchJourneyStepsParams } from '../actions/journey'; import { - Ping, + FailedStepsApiResponse, + FailedStepsApiResponseType, + JourneyStep, + JourneyStepType, + ScreenshotImageBlob, + ScreenshotRefImageData, SyntheticsJourneyApiResponse, SyntheticsJourneyApiResponseType, } from '../../../common/runtime_types'; @@ -16,23 +21,23 @@ import { export async function fetchJourneySteps( params: FetchJourneyStepsParams ): Promise { - return (await apiService.get( + return apiService.get( `/api/uptime/journey/${params.checkGroup}`, { syntheticEventTypes: params.syntheticEventTypes }, SyntheticsJourneyApiResponseType - )) as SyntheticsJourneyApiResponse; + ); } export async function fetchJourneysFailedSteps({ checkGroups, }: { checkGroups: string[]; -}): Promise { - return (await apiService.get( +}): Promise { + return apiService.get( `/api/uptime/journeys/failed_steps`, { checkGroups }, - SyntheticsJourneyApiResponseType - )) as SyntheticsJourneyApiResponse; + FailedStepsApiResponseType + ); } export async function fetchLastSuccessfulStep({ @@ -43,15 +48,21 @@ export async function fetchLastSuccessfulStep({ monitorId: string; timestamp: string; stepIndex: number; -}): Promise { - return (await apiService.get(`/api/uptime/synthetics/step/success/`, { - monitorId, - timestamp, - stepIndex, - })) as Ping; +}): Promise { + return await apiService.get( + `/api/uptime/synthetics/step/success/`, + { + monitorId, + timestamp, + stepIndex, + }, + JourneyStepType + ); } -export async function getJourneyScreenshot(imgSrc: string) { +export async function getJourneyScreenshot( + imgSrc: string +): Promise { try { const imgRequest = new Request(imgSrc); @@ -61,16 +72,22 @@ export async function getJourneyScreenshot(imgSrc: string) { return null; } - const imgBlob = await response.blob(); - + const contentType = response.headers.get('content-type'); const stepName = response.headers.get('caption-name'); - const maxSteps = response.headers.get('max-steps'); - - return { - stepName, - maxSteps: Number(maxSteps ?? 0), - src: URL.createObjectURL(imgBlob), - }; + const maxSteps = Number(response.headers.get('max-steps') ?? 0); + if (contentType?.indexOf('application/json') !== -1) { + return { + stepName, + maxSteps, + ref: await response.json(), + }; + } else { + return { + stepName, + maxSteps, + src: URL.createObjectURL(await response.blob()), + }; + } } catch (e) { return null; } diff --git a/x-pack/plugins/uptime/public/state/reducers/journey.ts b/x-pack/plugins/uptime/public/state/reducers/journey.ts index 361454e1b3fa1..98bbd93a24e0c 100644 --- a/x-pack/plugins/uptime/public/state/reducers/journey.ts +++ b/x-pack/plugins/uptime/public/state/reducers/journey.ts @@ -6,7 +6,7 @@ */ import { handleActions, Action } from 'redux-actions'; -import { Ping, SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; +import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; import { pruneJourneyState } from '../actions/journey'; import { FetchJourneyStepsParams, @@ -18,7 +18,7 @@ import { export interface JourneyState { checkGroup: string; - steps: Ping[]; + steps: JourneyStep[]; details?: SyntheticsJourneyApiResponse['details']; loading: boolean; error?: Error; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.test.ts new file mode 100644 index 0000000000000..4d678021ce784 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getJourneyDetails } from './get_journey_details'; +import { mockSearchResult } from './helper'; + +describe('getJourneyDetails', () => { + let mockData: unknown; + + beforeEach(() => { + mockData = { + _id: 'uTjtNHoBu1okBtVwOgMb', + _source: { + '@timestamp': '2021-06-22T18:13:19.013Z', + synthetics: { + package_version: '1.0.0-beta.2', + journey: { + name: 'inline', + id: 'inline', + }, + payload: { + source: + 'async ({ page, browser, params }) => {\n scriptFn.apply(null, [core_1.step, page, browser, params]);\n }', + params: {}, + }, + index: 0, + type: 'journey/start', + }, + monitor: { + name: 'My Monitor - inline', + timespan: { + lt: '2021-06-22T18:14:19.013Z', + gte: '2021-06-22T18:13:19.013Z', + }, + check_group: '85946468-d385-11eb-8848-acde48001122', + id: 'my-browser-monitor-inline', + type: 'browser', + status: 'up', + }, + }, + }; + }); + + it('formats ref detail data', async () => { + expect( + await getJourneyDetails({ + uptimeEsClient: mockSearchResult(mockData), + checkGroup: '85946468-d385-11eb-8848-acde48001122', + }) + ).toMatchInlineSnapshot(` + Object { + "journey": Object { + "@timestamp": "2021-06-22T18:13:19.013Z", + "_id": "uTjtNHoBu1okBtVwOgMb", + "monitor": Object { + "check_group": "85946468-d385-11eb-8848-acde48001122", + "id": "my-browser-monitor-inline", + "name": "My Monitor - inline", + "status": "up", + "timespan": Object { + "gte": "2021-06-22T18:13:19.013Z", + "lt": "2021-06-22T18:14:19.013Z", + }, + "type": "browser", + }, + "synthetics": Object { + "index": 0, + "journey": Object { + "id": "inline", + "name": "inline", + }, + "package_version": "1.0.0-beta.2", + "payload": Object { + "params": Object {}, + "source": "async ({ page, browser, params }) => { + scriptFn.apply(null, [core_1.step, page, browser, params]); + }", + }, + "type": "journey/start", + }, + }, + "next": Object { + "checkGroup": "85946468-d385-11eb-8848-acde48001122", + "timestamp": "2021-06-22T18:13:19.013Z", + }, + "previous": Object { + "checkGroup": "85946468-d385-11eb-8848-acde48001122", + "timestamp": "2021-06-22T18:13:19.013Z", + }, + "timestamp": "2021-06-22T18:13:19.013Z", + } + `); + }); + + it('returns null for 0 hits', async () => { + expect( + await getJourneyDetails({ uptimeEsClient: mockSearchResult([]), checkGroup: 'check_group' }) + ).toBe(null); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts index 6081cc3a7b7c8..b389699e2074a 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts @@ -7,7 +7,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; +import { + JourneyStep, + SyntheticsJourneyApiResponse, +} from '../../../common/runtime_types/ping/synthetics'; export interface GetJourneyDetails { checkGroup: string; @@ -39,8 +42,9 @@ export const getJourneyDetails: UMElasticsearchQueryFn< const { body: thisJourney } = await uptimeEsClient.search({ body: baseParams }); - if (thisJourney?.hits?.hits.length > 0) { - const thisJourneySource: any = thisJourney.hits.hits[0]._source; + if (thisJourney.hits.hits.length > 0) { + const { _id, _source } = thisJourney.hits.hits[0]; + const thisJourneySource = Object.assign({ _id }, _source) as JourneyStep; const baseSiblingParams = { query: { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.test.ts new file mode 100644 index 0000000000000..58e5202c2bca2 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getJourneyFailedSteps } from './get_journey_failed_steps'; +import { mockSearchResult } from './helper'; + +describe('getJourneyFailedSteps', () => { + let mockData: unknown; + + beforeEach(() => { + mockData = { + _id: 'uTjtNHoBu1okBtVwOgMb', + _source: { + '@timestamp': '2021-06-22T18:13:19.013Z', + synthetics: { + package_version: '1.0.0-beta.2', + journey: { + name: 'inline', + id: 'inline', + }, + payload: { + source: + 'async ({ page, browser, params }) => {\n scriptFn.apply(null, [core_1.step, page, browser, params]);\n }', + params: {}, + }, + index: 0, + type: 'journey/start', + }, + monitor: { + name: 'My Monitor - inline', + timespan: { + lt: '2021-06-22T18:14:19.013Z', + gte: '2021-06-22T18:13:19.013Z', + }, + check_group: '85946468-d385-11eb-8848-acde48001122', + id: 'my-browser-monitor-inline', + type: 'browser', + status: 'up', + }, + }, + }; + }); + + it('formats failed steps', async () => { + expect( + await getJourneyFailedSteps({ + uptimeEsClient: mockSearchResult(mockData), + checkGroups: ['chg1', 'chg2'], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "@timestamp": "2021-06-22T18:13:19.013Z", + "_id": "uTjtNHoBu1okBtVwOgMb", + "monitor": Object { + "check_group": "85946468-d385-11eb-8848-acde48001122", + "id": "my-browser-monitor-inline", + "name": "My Monitor - inline", + "status": "up", + "timespan": Object { + "gte": "2021-06-22T18:13:19.013Z", + "lt": "2021-06-22T18:14:19.013Z", + }, + "type": "browser", + }, + "synthetics": Object { + "index": 0, + "journey": Object { + "id": "inline", + "name": "inline", + }, + "package_version": "1.0.0-beta.2", + "payload": Object { + "params": Object {}, + "source": "async ({ page, browser, params }) => { + scriptFn.apply(null, [core_1.step, page, browser, params]); + }", + }, + "type": "journey/start", + }, + "timestamp": "2021-06-22T18:13:19.013Z", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts index d98e235460167..51e4fc671f048 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_failed_steps.ts @@ -6,19 +6,18 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { SearchHit } from '../../../../../../src/core/types/elasticsearch'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { Ping } from '../../../common/runtime_types'; +import { JourneyStep } from '../../../common/runtime_types/ping/synthetics'; export interface GetJourneyStepsParams { checkGroups: string[]; } -export const getJourneyFailedSteps: UMElasticsearchQueryFn = async ({ - uptimeEsClient, - checkGroups, -}) => { +export const getJourneyFailedSteps: UMElasticsearchQueryFn< + GetJourneyStepsParams, + JourneyStep[] +> = async ({ uptimeEsClient, checkGroups }) => { const params = { query: { bool: { @@ -53,11 +52,11 @@ export const getJourneyFailedSteps: UMElasticsearchQueryFn>).map((h) => { - const source = h._source as Ping & { '@timestamp': string }; + return result.hits.hits.map(({ _id, _source }) => { + const step = Object.assign({ _id }, _source) as JourneyStep; return { - ...source, - timestamp: source['@timestamp'], + ...step, + timestamp: step['@timestamp'], }; - }) as unknown) as Ping; + }); }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.test.ts new file mode 100644 index 0000000000000..3d8bc04a10565 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getJourneyScreenshot } from './get_journey_screenshot'; +import { mockSearchResult } from './helper'; + +describe('getJourneyScreenshot', () => { + it('returns screenshot data', async () => { + const screenshotResult = { + _id: 'id', + _source: { + synthetics: { + blob_mime: 'image/jpeg', + blob: 'image data', + step: { + name: 'load homepage', + }, + type: 'step/screenshot', + }, + }, + }; + expect( + await getJourneyScreenshot({ + uptimeEsClient: mockSearchResult([], { + // @ts-expect-error incomplete search result + step: { image: { hits: { hits: [screenshotResult] } } }, + }), + checkGroup: 'checkGroup', + stepIndex: 0, + }) + ).toEqual({ + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + step: { + name: 'load homepage', + }, + type: 'step/screenshot', + }, + totalSteps: 0, + }); + }); + + it('returns ref data', async () => { + const screenshotRefResult = { + _id: 'id', + _source: { + '@timestamp': '123', + monitor: { + check_group: 'check_group', + }, + screenshot_ref: { + width: 10, + height: 20, + blocks: [ + { + hash: 'hash1', + top: 0, + left: 0, + height: 2, + width: 4, + }, + { + hash: 'hash2', + top: 0, + left: 2, + height: 2, + width: 4, + }, + ], + }, + synthetics: { + package_version: 'v1.0.0', + step: { + name: 'name', + index: 0, + }, + type: 'step/screenshot_ref', + }, + }, + }; + expect( + await getJourneyScreenshot({ + uptimeEsClient: mockSearchResult([], { + // @ts-expect-error incomplete search result + step: { image: { hits: { hits: [screenshotRefResult] } } }, + }), + checkGroup: 'checkGroup', + stepIndex: 0, + }) + ).toMatchInlineSnapshot(` + Object { + "@timestamp": "123", + "monitor": Object { + "check_group": "check_group", + }, + "screenshot_ref": Object { + "blocks": Array [ + Object { + "hash": "hash1", + "height": 2, + "left": 0, + "top": 0, + "width": 4, + }, + Object { + "hash": "hash2", + "height": 2, + "left": 2, + "top": 0, + "width": 4, + }, + ], + "height": 20, + "width": 10, + }, + "synthetics": Object { + "package_version": "v1.0.0", + "step": Object { + "index": 0, + "name": "name", + }, + "type": "step/screenshot_ref", + }, + "totalSteps": 0, + } + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts index 2c56d41507165..3d95d35aa90d0 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot.ts @@ -6,30 +6,22 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { Ping } from '../../../common/runtime_types/ping'; +import { UMElasticsearchQueryFn } from '../adapters'; +import { RefResult, FullScreenshot } from '../../../common/runtime_types/ping/synthetics'; -export interface GetJourneyScreenshotParams { - checkGroup: string; - stepIndex: number; +interface ResultType { + _source: RefResult | FullScreenshot; } -export interface GetJourneyScreenshotResults { - blob: string | null; - mimeType: string | null; - stepName: string; - totalSteps: number; -} +export type ScreenshotReturnTypesUnion = + | ((FullScreenshot | RefResult) & { totalSteps: number }) + | null; export const getJourneyScreenshot: UMElasticsearchQueryFn< - GetJourneyScreenshotParams, - any -> = async ({ - uptimeEsClient, - checkGroup, - stepIndex, -}): Promise => { - const params = { + { checkGroup: string; stepIndex: number }, + ScreenshotReturnTypesUnion +> = async ({ checkGroup, stepIndex, uptimeEsClient }) => { + const body = { track_total_hits: true, size: 0, query: { @@ -41,8 +33,8 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< }, }, { - term: { - 'synthetics.type': 'step/screenshot', + terms: { + 'synthetics.type': ['step/screenshot', 'step/screenshot_ref'], }, }, ] as QueryDslQueryContainer[], @@ -59,25 +51,22 @@ export const getJourneyScreenshot: UMElasticsearchQueryFn< image: { top_hits: { size: 1, - _source: ['synthetics.blob', 'synthetics.blob_mime', 'synthetics.step.name'], }, }, }, }, }, }; - const { body: result } = await uptimeEsClient.search({ body: params }); - if (result?.hits?.total.value < 1) { - return null; - } + const result = await uptimeEsClient.search({ body }); + + const screenshotsOrRefs = + (result.body.aggregations?.step.image.hits.hits as ResultType[]) ?? null; - const stepHit = result?.aggregations?.step.image.hits.hits[0]?._source as Ping; + if (screenshotsOrRefs.length === 0) return null; return { - blob: stepHit?.synthetics?.blob ?? null, - mimeType: stepHit?.synthetics?.blob_mime ?? null, - stepName: stepHit?.synthetics?.step?.name ?? '', - totalSteps: result?.hits?.total.value, + ...screenshotsOrRefs[0]._source, + totalSteps: result.body.hits.total.value, }; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.test.ts new file mode 100644 index 0000000000000..32e4b730e80ab --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getJourneyScreenshotBlocks } from './get_journey_screenshot_blocks'; +import { mockSearchResult } from './helper'; + +describe('getJourneyScreenshotBlocks', () => { + it('returns formatted blocks', async () => { + expect( + await getJourneyScreenshotBlocks({ + uptimeEsClient: mockSearchResult([ + { + _id: 'hash1', + _source: { + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + }, + }, + }, + { + _id: 'hash2', + _source: { + synthetics: { + blob: 'image data', + blob_mime: 'image/jpeg', + }, + }, + }, + ]), + blockIds: ['hash1', 'hash2'], + }) + ).toMatchInlineSnapshot(` + Array [ + Object { + "id": "hash1", + "synthetics": Object { + "blob": "image data", + "blob_mime": "image/jpeg", + }, + }, + Object { + "id": "hash2", + "synthetics": Object { + "blob": "image data", + "blob_mime": "image/jpeg", + }, + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.ts new file mode 100644 index 0000000000000..3d904ef47e602 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_screenshot_blocks.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshotBlockDoc } from '../../../common/runtime_types/ping/synthetics'; +import { UMElasticsearchQueryFn } from '../adapters/framework'; + +interface ScreenshotBlockResultType { + _id: string; + _source: { + synthetics: { + blob: string; + blob_mime: string; + }; + }; +} + +export const getJourneyScreenshotBlocks: UMElasticsearchQueryFn< + { blockIds: string[] }, + ScreenshotBlockDoc[] +> = async ({ blockIds, uptimeEsClient }) => { + const body = { + query: { + bool: { + filter: [ + { + ids: { + values: blockIds, + }, + }, + ], + }, + }, + size: 1000, + }; + + const fetchScreenshotBlocksResult = await uptimeEsClient.search({ body }); + + return (fetchScreenshotBlocksResult.body.hits.hits as ScreenshotBlockResultType[]).map( + ({ _id, _source }) => ({ + id: _id, + synthetics: { + blob: _source.synthetics.blob, + blob_mime: _source.synthetics.blob_mime, + }, + }) + ); +}; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts index af7752b05997e..b14a13b50432d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { JourneyStep } from '../../../common/runtime_types/ping/synthetics'; import { getJourneySteps, formatSyntheticEvents } from './get_journey_steps'; import { getUptimeESMockClient } from './helper'; @@ -13,10 +14,11 @@ describe('getJourneySteps request module', () => { it('returns default steps if none are provided', () => { expect(formatSyntheticEvents()).toMatchInlineSnapshot(` Array [ - "step/end", "cmd/status", - "step/screenshot", "journey/browserconsole", + "step/end", + "step/screenshot", + "step/screenshot_ref", ] `); }); @@ -107,8 +109,8 @@ describe('getJourneySteps request module', () => { it('formats ES result', async () => { const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient(); - mockEsClient.search.mockResolvedValueOnce(data as any); - const result: any = await getJourneySteps({ + mockEsClient.search.mockResolvedValueOnce(data); + const result: JourneyStep[] = await getJourneySteps({ uptimeEsClient, checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d', }); @@ -120,10 +122,11 @@ describe('getJourneySteps request module', () => { Object { "terms": Object { "synthetics.type": Array [ - "step/end", "cmd/status", - "step/screenshot", "journey/browserconsole", + "step/end", + "step/screenshot", + "step/screenshot_ref", ], }, } @@ -156,10 +159,12 @@ describe('getJourneySteps request module', () => { expect(result).toHaveLength(2); // `getJourneySteps` is responsible for formatting these fields, so we need to check them - result.forEach((step: any) => { - expect(['2021-02-01T17:45:19.001Z', '2021-02-01T17:45:49.944Z']).toContain(step.timestamp); - expect(['o6myXncBFt2V8m6r6z-r', 'IjqzXncBn2sjqrYxYoCG']).toContain(step.docId); - expect(step.synthetics.screenshotExists).toBeDefined(); + result.forEach((step: JourneyStep) => { + expect(['2021-02-01T17:45:19.001Z', '2021-02-01T17:45:49.944Z']).toContain( + step['@timestamp'] + ); + expect(['o6myXncBFt2V8m6r6z-r', 'IjqzXncBn2sjqrYxYoCG']).toContain(step._id); + expect(step.synthetics.isFullScreenshot).toBeDefined(); }); }); @@ -168,9 +173,9 @@ describe('getJourneySteps request module', () => { data.body.hits.hits[0]._source.synthetics.type = 'step/screenshot'; data.body.hits.hits[0]._source.synthetics.step.index = 2; - mockEsClient.search.mockResolvedValueOnce(data as any); + mockEsClient.search.mockResolvedValueOnce(data); - const result: any = await getJourneySteps({ + const result: JourneyStep[] = await getJourneySteps({ uptimeEsClient, checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d', syntheticEventTypes: ['stderr', 'step/end'], @@ -191,7 +196,7 @@ describe('getJourneySteps request module', () => { `); expect(result).toHaveLength(1); - expect(result[0].synthetics.screenshotExists).toBe(true); + expect(result[0].synthetics.isFullScreenshot).toBe(true); }); }); }); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index 95aadc776fa76..fe77e2d63d2f1 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -6,17 +6,22 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; -import { SearchHit } from 'src/core/types/elasticsearch/search'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { Ping } from '../../../common/runtime_types'; +import { JourneyStep } from '../../../common/runtime_types/ping/synthetics'; export interface GetJourneyStepsParams { checkGroup: string; syntheticEventTypes?: string | string[]; } -const defaultEventTypes = ['step/end', 'cmd/status', 'step/screenshot', 'journey/browserconsole']; +const defaultEventTypes = [ + 'cmd/status', + 'journey/browserconsole', + 'step/end', + 'step/screenshot', + 'step/screenshot_ref', +]; export const formatSyntheticEvents = (eventTypes?: string | string[]) => { if (!eventTypes) { @@ -26,11 +31,12 @@ export const formatSyntheticEvents = (eventTypes?: string | string[]) => { } }; -export const getJourneySteps: UMElasticsearchQueryFn = async ({ - uptimeEsClient, - checkGroup, - syntheticEventTypes, -}) => { +type ResultType = JourneyStep & { '@timestamp': string }; + +export const getJourneySteps: UMElasticsearchQueryFn< + GetJourneyStepsParams, + JourneyStep[] +> = async ({ uptimeEsClient, checkGroup, syntheticEventTypes }) => { const params = { query: { bool: { @@ -53,28 +59,43 @@ export const getJourneySteps: UMElasticsearchQueryFn>) - .filter((h) => h._source?.synthetics?.type === 'step/screenshot') - .map((h) => h._source?.synthetics?.step?.index as number); + const steps = result.hits.hits.map( + ({ _id, _source }) => Object.assign({ _id }, _source) as ResultType + ); + + const screenshotIndexList: number[] = []; + const refIndexList: number[] = []; + const stepsWithoutImages: ResultType[] = []; - return ((result.hits.hits as Array>) - .filter((h) => h._source?.synthetics?.type !== 'step/screenshot') - .map((h) => { - const source = h._source as Ping & { '@timestamp': string }; - return { - ...source, - timestamp: source['@timestamp'], - docId: h._id, - synthetics: { - ...source.synthetics, - screenshotExists: screenshotIndexes.some((i) => i === source.synthetics?.step?.index), - }, - }; - }) as unknown) as Ping; + /** + * Store screenshot indexes, we use these to determine if a step has a screenshot below. + * Store steps that are not screenshots, we return these to the client. + */ + for (const step of steps) { + const { synthetics } = step; + if (synthetics.type === 'step/screenshot' && synthetics?.step?.index) { + screenshotIndexList.push(synthetics.step.index); + } else if (synthetics.type === 'step/screenshot_ref' && synthetics?.step?.index) { + refIndexList.push(synthetics.step.index); + } else { + stepsWithoutImages.push(step); + } + } + + return stepsWithoutImages.map(({ _id, ...rest }) => ({ + _id, + ...rest, + timestamp: rest['@timestamp'], + synthetics: { + ...rest.synthetics, + isFullScreenshot: screenshotIndexList.some((i) => i === rest?.synthetics?.step?.index), + isScreenshotRef: refIndexList.some((i) => i === rest?.synthetics?.step?.index), + }, + })); }; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts index 6f88e7e37e55e..6d0f72052e586 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts @@ -7,7 +7,7 @@ import { estypes } from '@elastic/elasticsearch'; import { UMElasticsearchQueryFn } from '../adapters/framework'; -import { Ping } from '../../../common/runtime_types/ping'; +import { JourneyStep } from '../../../common/runtime_types/ping'; export interface GetStepScreenshotParams { monitorId: string; @@ -17,7 +17,7 @@ export interface GetStepScreenshotParams { export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< GetStepScreenshotParams, - any + JourneyStep | null > = async ({ uptimeEsClient, monitorId, stepIndex, timestamp }) => { const lastSuccessCheckParams: estypes.SearchRequest['body'] = { size: 1, @@ -65,11 +65,11 @@ export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< const { body: result } = await uptimeEsClient.search({ body: lastSuccessCheckParams }); - if (result?.hits?.total.value < 1) { + if (result.hits.total.value < 1) { return null; } - const step = result?.hits.hits[0]._source as Ping & { '@timestamp': string }; + const step = result.hits.hits[0]._source as JourneyStep & { '@timestamp': string }; return { ...step, diff --git a/x-pack/plugins/uptime/server/lib/requests/helper.ts b/x-pack/plugins/uptime/server/lib/requests/helper.ts index c637c05094667..c33b15200a781 100644 --- a/x-pack/plugins/uptime/server/lib/requests/helper.ts +++ b/x-pack/plugins/uptime/server/lib/requests/helper.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SearchResponse } from '@elastic/elasticsearch/api/types'; +import { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/api/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ElasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { @@ -81,3 +81,34 @@ export const getUptimeESMockClient = ( }), }; }; + +export function mockSearchResult( + data: unknown, + aggregations: Record = {} +): UptimeESClient { + const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient(); + + // @ts-expect-error incomplete search response + mockEsClient.search.mockResolvedValue({ + body: { + took: 18, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + hits: Array.isArray(data) ? data : [data], + max_score: 0.0, + total: { + value: Array.isArray(data) ? data.length : 0, + relation: 'gte', + }, + }, + aggregations, + }, + }); + return uptimeEsClient; +} diff --git a/x-pack/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts index 24109245c2902..587a4301ba9e5 100644 --- a/x-pack/plugins/uptime/server/lib/requests/index.ts +++ b/x-pack/plugins/uptime/server/lib/requests/index.ts @@ -25,6 +25,7 @@ import { getJourneyDetails } from './get_journey_details'; import { getNetworkEvents } from './get_network_events'; import { getJourneyFailedSteps } from './get_journey_failed_steps'; import { getStepLastSuccessfulStep } from './get_last_successful_step'; +import { getJourneyScreenshotBlocks } from './get_journey_screenshot_blocks'; export const requests = { getCerts, @@ -45,6 +46,7 @@ export const requests = { getJourneyFailedSteps, getStepLastSuccessfulStep, getJourneyScreenshot, + getJourneyScreenshotBlocks, getJourneyDetails, getNetworkEvents, }; diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 91b5597321ed0..d4d0e13bd23db 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -12,6 +12,7 @@ import { createGetPingsRoute, createJourneyRoute, createJourneyScreenshotRoute, + createJourneyScreenshotBlockRoute, } from './pings'; import { createGetDynamicSettingsRoute, createPostDynamicSettingsRoute } from './dynamic_settings'; import { createLogPageViewRoute } from './telemetry'; @@ -51,6 +52,7 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ createGetMonitorDurationRoute, createJourneyRoute, createJourneyScreenshotRoute, + createJourneyScreenshotBlockRoute, createNetworkEventsRoute, createJourneyFailedStepsRoute, createLastSuccessfulStepRoute, diff --git a/x-pack/plugins/uptime/server/rest_api/pings/index.ts b/x-pack/plugins/uptime/server/rest_api/pings/index.ts index 61e3cea6d0251..45cd23dea42ed 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/index.ts @@ -9,3 +9,4 @@ export { createGetPingsRoute } from './get_pings'; export { createGetPingHistogramRoute } from './get_ping_histogram'; export { createJourneyRoute } from './journeys'; export { createJourneyScreenshotRoute } from './journey_screenshots'; +export { createJourneyScreenshotBlockRoute } from './journey_screenshot_blocks'; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.ts new file mode 100644 index 0000000000000..63c2cfe7e2d48 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshot_blocks.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { isRight } from 'fp-ts/lib/Either'; +import { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; +import { ScreenshotBlockDoc } from '../../../common/runtime_types/ping/synthetics'; + +export const createJourneyScreenshotBlockRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/journey/screenshot/block', + validate: { + query: schema.object({ + hash: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + _inspect: schema.maybe(schema.boolean()), + }), + }, + handler: async ({ request, response, uptimeEsClient }) => { + const { hash } = request.query; + + const decoded = t.union([t.string, t.array(t.string)]).decode(hash); + if (!isRight(decoded)) { + return response.badRequest(); + } + const { right: data } = decoded; + let result: ScreenshotBlockDoc[]; + try { + result = await libs.requests.getJourneyScreenshotBlocks({ + blockIds: Array.isArray(data) ? data : [data], + uptimeEsClient, + }); + } catch (e: unknown) { + return response.custom({ statusCode: 500, body: { message: e } }); + } + if (result.length === 0) { + return response.notFound(); + } + return response.ok({ + body: result, + headers: { + // we can cache these blocks with extreme prejudice as they are inherently unchanging + // when queried by ID, since the ID is the hash of the data + 'Cache-Control': 'max-age=604800', + }, + }); + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts index 4d8c8f86ddf2d..bd7cf6af4f843 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts @@ -6,9 +6,23 @@ */ import { schema } from '@kbn/config-schema'; +import { + isRefResult, + isFullScreenshot, + ScreenshotBlockDoc, +} from '../../../common/runtime_types/ping/synthetics'; import { UMServerLibs } from '../../lib/lib'; +import { ScreenshotReturnTypesUnion } from '../../lib/requests/get_journey_screenshot'; import { UMRestApiRouteFactory } from '../types'; +function getSharedHeaders(stepName: string, totalSteps: number) { + return { + 'cache-control': 'max-age=600', + 'caption-name': stepName, + 'max-steps': String(totalSteps), + }; +} + export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', path: '/api/uptime/journey/screenshot/{checkGroup}/{stepIndex}', @@ -25,23 +39,49 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ handler: async ({ uptimeEsClient, request, response }) => { const { checkGroup, stepIndex } = request.params; - const result = await libs.requests.getJourneyScreenshot({ - uptimeEsClient, - checkGroup, - stepIndex, - }); + let result: ScreenshotReturnTypesUnion | null = null; + try { + result = await libs.requests.getJourneyScreenshot({ + uptimeEsClient, + checkGroup, + stepIndex, + }); + } catch (e) { + return response.customError({ body: { message: e }, statusCode: 500 }); + } + + if (isFullScreenshot(result)) { + if (!result.synthetics.blob) { + return response.notFound(); + } - if (result === null || !result.blob) { - return response.notFound(); + return response.ok({ + body: Buffer.from(result.synthetics.blob, 'base64'), + headers: { + 'content-type': result.synthetics.blob_mime || 'image/png', // falls back to 'image/png' for earlier versions of synthetics + ...getSharedHeaders(result.synthetics.step.name, result.totalSteps), + }, + }); + } else if (isRefResult(result)) { + const blockIds = result.screenshot_ref.blocks.map(({ hash }) => hash); + let blocks: ScreenshotBlockDoc[]; + try { + blocks = await libs.requests.getJourneyScreenshotBlocks({ + uptimeEsClient, + blockIds, + }); + } catch (e: unknown) { + return response.custom({ statusCode: 500, body: { message: e } }); + } + return response.ok({ + body: { + screenshotRef: result, + blocks, + }, + headers: getSharedHeaders(result.synthetics.step.name, result.totalSteps ?? 0), + }); } - return response.ok({ - body: Buffer.from(result.blob, 'base64'), - headers: { - 'content-type': result.mimeType || 'image/png', // falls back to 'image/png' for earlier versions of synthetics - 'cache-control': 'max-age=600', - 'caption-name': result.stepName, - 'max-steps': result.totalSteps, - }, - }); + + return response.notFound(); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts index 31555be25b2ff..284feda2c662b 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts @@ -25,27 +25,31 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => _inspect: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient, request }): Promise => { + handler: async ({ uptimeEsClient, request, response }): Promise => { const { checkGroup } = request.params; const { syntheticEventTypes } = request.query; - const [result, details] = await Promise.all([ - await libs.requests.getJourneySteps({ - uptimeEsClient, - checkGroup, - syntheticEventTypes, - }), - await libs.requests.getJourneyDetails({ - uptimeEsClient, - checkGroup, - }), - ]); + try { + const [result, details] = await Promise.all([ + await libs.requests.getJourneySteps({ + uptimeEsClient, + checkGroup, + syntheticEventTypes, + }), + await libs.requests.getJourneyDetails({ + uptimeEsClient, + checkGroup, + }), + ]); - return { - checkGroup, - steps: result, - details, - }; + return { + checkGroup, + steps: result, + details, + }; + } catch (e: unknown) { + return response.custom({ statusCode: 500, body: { message: e } }); + } }, }); @@ -58,16 +62,19 @@ export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMSer _inspect: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient, request }): Promise => { + handler: async ({ uptimeEsClient, request, response }): Promise => { const { checkGroups } = request.query; - const result = await libs.requests.getJourneyFailedSteps({ - uptimeEsClient, - checkGroups, - }); - - return { - checkGroups, - steps: result, - }; + try { + const result = await libs.requests.getJourneyFailedSteps({ + uptimeEsClient, + checkGroups, + }); + return { + checkGroups, + steps: result, + }; + } catch (e) { + return response.customError({ statusCode: 500, body: e }); + } }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts index c326037b9ecbf..cb90de50e2510 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts @@ -6,6 +6,11 @@ */ import { schema } from '@kbn/config-schema'; +import { + isRefResult, + isFullScreenshot, + JourneyStep, +} from '../../../common/runtime_types/ping/synthetics'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; @@ -23,11 +28,36 @@ export const createLastSuccessfulStepRoute: UMRestApiRouteFactory = (libs: UMSer handler: async ({ uptimeEsClient, request, response }) => { const { timestamp, monitorId, stepIndex } = request.query; - return await libs.requests.getStepLastSuccessfulStep({ + const step: JourneyStep | null = await libs.requests.getStepLastSuccessfulStep({ uptimeEsClient, monitorId, stepIndex, timestamp, }); + + if (step === null) { + return response.notFound(); + } + + if (!step.synthetics?.step?.index) { + return response.ok({ body: step }); + } + + const screenshot = await libs.requests.getJourneyScreenshot({ + uptimeEsClient, + checkGroup: step.monitor.check_group, + stepIndex: step.synthetics.step.index, + }); + + if (screenshot === null) { + return response.ok({ body: step }); + } + + step.synthetics.isScreenshotRef = isRefResult(screenshot); + step.synthetics.isFullScreenshot = isFullScreenshot(screenshot); + + return response.ok({ + body: step, + }); }, }); From 5aa83052793790cffe6ae5c0e037527425485c23 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 29 Jun 2021 14:14:18 +0200 Subject: [PATCH 57/74] [Exploratory vie] Added synthetics metrics (#103482) --- .../constants/field_names/synthetics.ts | 5 ++ .../configurations/constants/labels.ts | 11 ++++ .../synthetics/data_distribution_config.ts | 52 +++++++++++++++-- .../synthetics/field_formats.ts | 58 +++++++++++++++++++ .../synthetics/kpi_over_time_config.ts | 55 ++++++++++++++++-- .../hooks/use_lens_attributes.ts | 15 +++-- .../columns/report_definition_field.tsx | 8 ++- .../field_value_combobox.tsx | 25 +++++--- .../shared/field_value_suggestions/index.tsx | 2 + .../shared/field_value_suggestions/types.ts | 1 + 10 files changed, 208 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts index edf8b7fb9d741..eff73d242de75 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/field_names/synthetics.ts @@ -6,3 +6,8 @@ */ export const MONITOR_DURATION_US = 'monitor.duration.us'; +export const SYNTHETICS_CLS = 'browser.experience.cls'; +export const SYNTHETICS_LCP = 'browser.experience.lcp.us'; +export const SYNTHETICS_FCP = 'browser.experience.fcp.us'; +export const SYNTHETICS_DOCUMENT_ONLOAD = 'browser.experience.load.us'; +export const SYNTHETICS_DCL = 'browser.experience.dcl.us'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts index eb8af4f26c01a..5a3e773f7d8ee 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts @@ -72,6 +72,17 @@ export const CLS_LABEL = i18n.translate('xpack.observability.expView.fieldLabels defaultMessage: 'Cumulative layout shift', }); +export const DCL_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.dcl', { + defaultMessage: 'DOM content loaded', +}); + +export const DOCUMENT_ONLOAD_LABEL = i18n.translate( + 'xpack.observability.expView.fieldLabels.onload', + { + defaultMessage: 'Document complete (onLoad)', + } +); + export const BACKEND_TIME_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.backend', { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts index 2522f0b2c2581..730e742f9d8c5 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/data_distribution_config.ts @@ -7,8 +7,23 @@ import { ConfigProps, SeriesConfig } from '../../types'; import { FieldLabels, REPORT_METRIC_FIELD, RECORDS_PERCENTAGE_FIELD } from '../constants'; -import { buildExistsFilter } from '../utils'; -import { MONITORS_DURATION_LABEL, PINGS_LABEL } from '../constants/labels'; +import { + CLS_LABEL, + DCL_LABEL, + DOCUMENT_ONLOAD_LABEL, + FCP_LABEL, + LCP_LABEL, + MONITORS_DURATION_LABEL, + PINGS_LABEL, +} from '../constants/labels'; +import { + MONITOR_DURATION_US, + SYNTHETICS_CLS, + SYNTHETICS_DCL, + SYNTHETICS_DOCUMENT_ONLOAD, + SYNTHETICS_FCP, + SYNTHETICS_LCP, +} from '../constants/field_names/synthetics'; export function getSyntheticsDistributionConfig({ series, @@ -37,10 +52,39 @@ export function getSyntheticsDistributionConfig({ 'tags', 'url.port', ], - baseFilters: [...buildExistsFilter('summary.up', indexPattern)], + baseFilters: [], definitionFields: ['monitor.name', 'url.full'], metricOptions: [ - { label: 'Monitor duration', id: 'monitor.duration.us', field: 'monitor.duration.us' }, + { + label: MONITORS_DURATION_LABEL, + id: MONITOR_DURATION_US, + field: MONITOR_DURATION_US, + }, + { + label: LCP_LABEL, + field: SYNTHETICS_LCP, + id: SYNTHETICS_LCP, + }, + { + label: FCP_LABEL, + field: SYNTHETICS_FCP, + id: SYNTHETICS_FCP, + }, + { + label: DCL_LABEL, + field: SYNTHETICS_DCL, + id: SYNTHETICS_DCL, + }, + { + label: DOCUMENT_ONLOAD_LABEL, + field: SYNTHETICS_DOCUMENT_ONLOAD, + id: SYNTHETICS_DOCUMENT_ONLOAD, + }, + { + label: CLS_LABEL, + field: SYNTHETICS_CLS, + id: SYNTHETICS_CLS, + }, ], labels: { ...FieldLabels, 'monitor.duration.us': MONITORS_DURATION_LABEL }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts index 5c91e3924cbb7..5f8a6a28ca81d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts @@ -6,6 +6,12 @@ */ import { FieldFormat } from '../../types'; +import { + SYNTHETICS_DCL, + SYNTHETICS_DOCUMENT_ONLOAD, + SYNTHETICS_FCP, + SYNTHETICS_LCP, +} from '../constants/field_names/synthetics'; export const syntheticsFieldFormats: FieldFormat[] = [ { @@ -21,4 +27,56 @@ export const syntheticsFieldFormats: FieldFormat[] = [ }, }, }, + { + field: SYNTHETICS_LCP, + format: { + id: 'duration', + params: { + inputFormat: 'microseconds', + outputFormat: 'humanizePrecise', + outputPrecision: 1, + showSuffix: true, + useShortSuffix: true, + }, + }, + }, + { + field: SYNTHETICS_FCP, + format: { + id: 'duration', + params: { + inputFormat: 'microseconds', + outputFormat: 'humanizePrecise', + outputPrecision: 1, + showSuffix: true, + useShortSuffix: true, + }, + }, + }, + { + field: SYNTHETICS_DOCUMENT_ONLOAD, + format: { + id: 'duration', + params: { + inputFormat: 'microseconds', + outputFormat: 'humanizePrecise', + outputPrecision: 1, + showSuffix: true, + useShortSuffix: true, + }, + }, + }, + { + field: SYNTHETICS_DCL, + format: { + id: 'duration', + params: { + inputFormat: 'microseconds', + outputFormat: 'humanizePrecise', + outputPrecision: 1, + showSuffix: true, + useShortSuffix: true, + }, + }, + }, ]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index 6bf280e93eb11..4ee22181d4334 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -7,9 +7,24 @@ import { ConfigProps, SeriesConfig } from '../../types'; import { FieldLabels, OPERATION_COLUMN, REPORT_METRIC_FIELD } from '../constants'; -import { buildExistsFilter } from '../utils'; -import { DOWN_LABEL, MONITORS_DURATION_LABEL, UP_LABEL } from '../constants/labels'; -import { MONITOR_DURATION_US } from '../constants/field_names/synthetics'; +import { + CLS_LABEL, + DCL_LABEL, + DOCUMENT_ONLOAD_LABEL, + DOWN_LABEL, + FCP_LABEL, + LCP_LABEL, + MONITORS_DURATION_LABEL, + UP_LABEL, +} from '../constants/labels'; +import { + MONITOR_DURATION_US, + SYNTHETICS_CLS, + SYNTHETICS_DCL, + SYNTHETICS_DOCUMENT_ONLOAD, + SYNTHETICS_FCP, + SYNTHETICS_LCP, +} from '../constants/field_names/synthetics'; const SUMMARY_UP = 'summary.up'; const SUMMARY_DOWN = 'summary.down'; @@ -29,8 +44,8 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon ], hasOperationType: false, filterFields: ['observer.geo.name', 'monitor.type', 'tags'], - breakdownFields: ['observer.geo.name', 'monitor.type'], - baseFilters: [...buildExistsFilter('summary.up', indexPattern)], + breakdownFields: ['observer.geo.name', 'monitor.type', 'monitor.name'], + baseFilters: [], palette: { type: 'palette', name: 'status' }, definitionFields: ['monitor.name', 'url.full'], metricOptions: [ @@ -52,6 +67,36 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon label: DOWN_LABEL, columnType: OPERATION_COLUMN, }, + { + label: LCP_LABEL, + field: SYNTHETICS_LCP, + id: SYNTHETICS_LCP, + columnType: OPERATION_COLUMN, + }, + { + label: FCP_LABEL, + field: SYNTHETICS_FCP, + id: SYNTHETICS_FCP, + columnType: OPERATION_COLUMN, + }, + { + label: DCL_LABEL, + field: SYNTHETICS_DCL, + id: SYNTHETICS_DCL, + columnType: OPERATION_COLUMN, + }, + { + label: DOCUMENT_ONLOAD_LABEL, + field: SYNTHETICS_DOCUMENT_ONLOAD, + id: SYNTHETICS_DOCUMENT_ONLOAD, + columnType: OPERATION_COLUMN, + }, + { + label: CLS_LABEL, + field: SYNTHETICS_CLS, + id: SYNTHETICS_CLS, + columnType: OPERATION_COLUMN, + }, ], labels: { ...FieldLabels }, }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index d14a26d13d928..8bb265b4f6d89 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -14,14 +14,17 @@ import { getDefaultConfigs } from '../configurations/default_configs'; import { SeriesUrl, UrlFilter } from '../types'; import { useAppIndexPatternContext } from './use_app_index_pattern'; +import { ALL_VALUES_SELECTED } from '../../field_value_suggestions/field_value_combobox'; export const getFiltersFromDefs = (reportDefinitions: SeriesUrl['reportDefinitions']) => { - return Object.entries(reportDefinitions ?? {}).map(([field, value]) => { - return { - field, - values: value, - }; - }) as UrlFilter[]; + return Object.entries(reportDefinitions ?? {}) + .map(([field, value]) => { + return { + field, + values: value, + }; + }) + .filter(({ values }) => !values.includes(ALL_VALUES_SELECTED)) as UrlFilter[]; }; export const useLensAttributes = (): TypedLensByValueInput['attributes'] | null => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx index 61f6f85dbeaf2..d137b36a7e8c7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_field.tsx @@ -16,6 +16,7 @@ import { PersistableFilter } from '../../../../../../../lens/common'; import { ExistsFilter } from '../../../../../../../../../src/plugins/data/common/es_query/filters'; import { buildPhrasesFilter } from '../../configurations/utils'; import { SeriesConfig } from '../../types'; +import { ALL_VALUES_SELECTED } from '../../../field_value_suggestions/field_value_combobox'; interface Props { seriesId: string; @@ -51,8 +52,10 @@ export function ReportDefinitionField({ seriesId, field, seriesConfig, onChange definitionFields.forEach((fieldT) => { if (indexPattern && selectedReportDefinitions?.[fieldT] && fieldT !== field) { const values = selectedReportDefinitions?.[fieldT]; - const valueFilter = buildPhrasesFilter(fieldT, values, indexPattern)[0]; - filtersN.push(valueFilter.query); + if (!values.includes(ALL_VALUES_SELECTED)) { + const valueFilter = buildPhrasesFilter(fieldT, values, indexPattern)[0]; + filtersN.push(valueFilter.query); + } } }); } @@ -74,6 +77,7 @@ export function ReportDefinitionField({ seriesId, field, seriesConfig, onChange filters={queryFilters} time={series.time} fullWidth={true} + allowAllValuesSelection={true} /> )}
    diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx index 393d4fc86a98d..fc562fa80e26d 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_combobox.tsx @@ -11,9 +11,11 @@ import { EuiComboBox, EuiFormControlLayout, EuiComboBoxOptionOption } from '@ela import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { FieldValueSelectionProps } from './types'; - -const formatOptions = (values?: string[]) => { - const uniqueValues = Array.from(new Set(values)); +export const ALL_VALUES_SELECTED = 'ALL_VALUES'; +const formatOptions = (values?: string[], allowAllValuesSelection?: boolean) => { + const uniqueValues = Array.from( + new Set(allowAllValuesSelection ? ['ALL_VALUES', ...(values ?? [])] : values) + ); return (uniqueValues ?? []).map((label) => ({ label, @@ -29,15 +31,24 @@ export function FieldValueCombobox({ values, setQuery, compressed = true, + allowAllValuesSelection, onChange: onSelectionChange, }: FieldValueSelectionProps) { - const [options, setOptions] = useState( - formatOptions(union(values?.map(({ label: lb }) => lb) ?? [], selectedValue ?? [])) + const [options, setOptions] = useState(() => + formatOptions( + union(values?.map(({ label: lb }) => lb) ?? [], selectedValue ?? []), + allowAllValuesSelection + ) ); useEffect(() => { - setOptions(formatOptions(union(values?.map(({ label: lb }) => lb) ?? [], selectedValue ?? []))); - }, [selectedValue, values]); + setOptions( + formatOptions( + union(values?.map(({ label: lb }) => lb) ?? [], selectedValue ?? []), + allowAllValuesSelection + ) + ); + }, [allowAllValuesSelection, selectedValue, values]); const onChange = (selectedValuesN: ValueOption[]) => { onSelectionChange(selectedValuesN.map(({ label: lbl }) => lbl)); diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx index 7cd41af78bef1..2b4d9c0898f3e 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx @@ -30,6 +30,7 @@ export function FieldValueSuggestions({ singleSelection, compressed, asFilterButton, + allowAllValuesSelection, asCombobox = true, onChange: onSelectionChange, }: FieldValueSuggestionsProps) { @@ -73,6 +74,7 @@ export function FieldValueSuggestions({ width={width} compressed={compressed} asFilterButton={asFilterButton} + allowAllValuesSelection={allowAllValuesSelection} /> ); } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index ab24f4064c02e..aa8c967e31ff5 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -23,6 +23,7 @@ interface CommonProps { compressed?: boolean; asFilterButton?: boolean; showCount?: boolean; + allowAllValuesSelection?: boolean; } export type FieldValueSuggestionsProps = CommonProps & { From af62b05e3a72788a88ea97e7699365fffff45fac Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Tue, 29 Jun 2021 08:24:51 -0400 Subject: [PATCH 58/74] Dont record analytics when showing curations (#103558) --- .../components/curations/curation/curation_logic.test.ts | 5 ++++- .../components/curations/curation/curation_logic.ts | 3 ++- .../enterprise_search/server/routes/app_search/curations.ts | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index db387f581b92e..c733294af2910 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -287,7 +287,10 @@ describe('CurationLogic', () => { await nextTick(); expect(http.get).toHaveBeenCalledWith( - '/api/app_search/engines/some-engine/curations/cur-123456789' + '/api/app_search/engines/some-engine/curations/cur-123456789', + { + query: { skip_record_analytics: 'true' }, + } ); expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts index 9fa2d353b4ef2..12ff380ccb389 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts @@ -169,7 +169,8 @@ export const CurationLogic = kea Date: Tue, 29 Jun 2021 08:55:30 -0400 Subject: [PATCH 59/74] [APM] Fleet: Add secret_token to agent instructions in legacy Getting Started guide (#102669) * Register tutorial on APM plugin * using files from apm * removing tutorial from apm_oss * removing export * fixing i18n * adding fleet section * adding fleet information on APM tutorial * adding fleet typing * fixing i18n * adding fleet information on APM tutorial * checks apm fleet integration when pushing button * adding fleet information on APM tutorial * refactoring * registering status check callback * addin custom component registration function * fixing TS issue * addressing PR comments * fixing tests * adding i18n * fixing issues * adding environment credencials * refactoring * adjusting size * adding unit test * adding unit test * refactoring * addressing PR comments * refactoring eui component * adding unit test * fixing TS issue * fixing TS issue * adding help text * renaming * moving tutorial to a common directory * moving files * updating apm int version * adding storybook * adding storybook * refactoring * removing commented code * fixing unit tests * addressing PR comments * fixing lint errors * changing to url * addressing PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../instruction_set.test.js.snap | 24 + .../__snapshots__/tutorial.test.js.snap | 3 + .../components/tutorial/instruction.js | 23 +- .../components/tutorial/instruction_set.js | 3 + .../tutorial/instruction_set.test.js | 6 + .../components/tutorial/tutorial.js | 1 + x-pack/plugins/apm/public/plugin.ts | 10 + .../tutorial/config_agent/commands/django.ts | 72 +++ .../tutorial/config_agent/commands/dotnet.ts | 14 + .../tutorial/config_agent/commands/flask.ts | 70 +++ .../commands/get_commands.test.ts | 536 ++++++++++++++++++ .../config_agent/commands/get_commands.ts | 48 ++ .../tutorial/config_agent/commands/go.ts | 58 ++ .../tutorial/config_agent/commands/java.ts | 14 + .../tutorial/config_agent/commands/node.ts | 57 ++ .../tutorial/config_agent/commands/php.ts | 11 + .../tutorial/config_agent/commands/rack.ts | 50 ++ .../tutorial/config_agent/commands/rails.ts | 21 + .../tutorial/config_agent/commands/rum.ts | 58 ++ .../config_agent/config_agent.stories.tsx | 117 ++++ .../tutorial/config_agent/copy_commands.tsx | 26 + .../config_agent/get_policy_options.test.ts | 318 +++++++++++ .../config_agent/get_policy_options.ts | 67 +++ .../tutorial/config_agent/index.test.tsx | 205 +++++++ .../public/tutorial/config_agent/index.tsx | 144 +++++ .../tutorial/config_agent/policy_selector.tsx | 92 +++ .../tutorial/config_agent/rum_script.tsx | 33 ++ .../tutorial_fleet_instructions.stories.tsx | 46 ++ .../apm/server/lib/fleet/get_agents.ts | 34 ++ x-pack/plugins/apm/server/routes/fleet.ts | 69 ++- .../instructions/apm_agent_instructions.ts | 361 +----------- x-pack/plugins/fleet/server/mocks/index.ts | 1 + x-pack/plugins/fleet/server/plugin.ts | 1 + x-pack/plugins/fleet/server/services/index.ts | 1 + 34 files changed, 2229 insertions(+), 365 deletions(-) create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/dotnet.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.test.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/go.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/php.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rack.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rails.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rum.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/index.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/rum_script.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/tutorial_fleet_instructions.stories.tsx create mode 100644 x-pack/plugins/apm/server/lib/fleet/get_agents.ts diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/instruction_set.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/instruction_set.test.js.snap index a9f9823047c0b..073d20b4bf804 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/instruction_set.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/instruction_set.test.js.snap @@ -52,8 +52,10 @@ exports[`render 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -65,8 +67,10 @@ exports[`render 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", @@ -129,8 +133,10 @@ exports[`statusCheckState checking status 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -142,8 +148,10 @@ exports[`statusCheckState checking status 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", @@ -236,8 +244,10 @@ exports[`statusCheckState failed status check - error 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -249,8 +259,10 @@ exports[`statusCheckState failed status check - error 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", @@ -347,8 +359,10 @@ exports[`statusCheckState failed status check - no data 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -360,8 +374,10 @@ exports[`statusCheckState failed status check - no data 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", @@ -458,8 +474,10 @@ exports[`statusCheckState initial state - no check has been attempted 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -471,8 +489,10 @@ exports[`statusCheckState initial state - no check has been attempted 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", @@ -565,8 +585,10 @@ exports[`statusCheckState successful status check 1`] = ` "do stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 0, "title": "step 1", @@ -578,8 +600,10 @@ exports[`statusCheckState successful status check 1`] = ` "do more stuff in command line", ] } + isCloudEnabled={false} paramValues={Object {}} replaceTemplateStrings={[Function]} + variantId="OSX" />, "key": 1, "title": "step 2", diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/tutorial.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/tutorial.test.js.snap index f819569cd422d..ac697fae17f69 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/tutorial.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/tutorial.test.js.snap @@ -31,6 +31,7 @@ exports[`isCloudEnabled is false should not render instruction toggle when ON_PR }, ] } + isCloudEnabled={false} key="0" offset={1} onStatusCheck={[Function]} @@ -107,6 +108,7 @@ exports[`isCloudEnabled is false should render ON_PREM instructions with instruc }, ] } + isCloudEnabled={false} key="0" offset={1} onStatusCheck={[Function]} @@ -154,6 +156,7 @@ exports[`should render ELASTIC_CLOUD instructions when isCloudEnabled is true 1` }, ] } + isCloudEnabled={true} key="0" offset={1} onStatusCheck={[Function]} diff --git a/src/plugins/home/public/application/components/tutorial/instruction.js b/src/plugins/home/public/application/components/tutorial/instruction.js index 373f8c318a504..e4b3b3f321bf9 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction.js +++ b/src/plugins/home/public/application/components/tutorial/instruction.js @@ -18,6 +18,7 @@ import { EuiCopy, EuiButton, EuiLoadingSpinner, + EuiErrorBoundary, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -31,6 +32,8 @@ export function Instruction({ textPre, replaceTemplateStrings, customComponentName, + variantId, + isCloudEnabled, }) { const { tutorialService, http, uiSettings, getBasePath } = getServices(); @@ -96,18 +99,22 @@ export function Instruction({ {commandBlock} - {post} - {LazyCustomComponent && ( }> - + + + )} + {post} +
    ); @@ -120,4 +127,6 @@ Instruction.propTypes = { textPre: PropTypes.string, replaceTemplateStrings: PropTypes.func.isRequired, customComponentName: PropTypes.string, + variantId: PropTypes.string, + isCloudEnabled: PropTypes.bool.isRequired, }; diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.js b/src/plugins/home/public/application/components/tutorial/instruction_set.js index da368120d493c..08b55a527b3cf 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.js @@ -187,6 +187,8 @@ class InstructionSetUi extends React.Component { textPost={instruction.textPost} replaceTemplateStrings={this.props.replaceTemplateStrings} customComponentName={instruction.customComponentName} + variantId={instructionVariant.id} + isCloudEnabled={this.props.isCloudEnabled} /> ); return { @@ -320,6 +322,7 @@ InstructionSetUi.propTypes = { paramValues: PropTypes.object.isRequired, setParameter: PropTypes.func, replaceTemplateStrings: PropTypes.func.isRequired, + isCloudEnabled: PropTypes.bool.isRequired, }; export const InstructionSet = injectI18n(InstructionSetUi); diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js index 539732a1c51a9..1bce4f72fde60 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js @@ -49,6 +49,7 @@ test('render', () => { offset={1} paramValues={{}} replaceTemplateStrings={() => {}} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -74,6 +75,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.FETCHING} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -90,6 +92,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.FETCHING} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -106,6 +109,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.ERROR} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -122,6 +126,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.NO_DATA} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -138,6 +143,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.HAS_DATA} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js index 92bbb92fa0850..52daa53d4585c 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.js @@ -301,6 +301,7 @@ class TutorialUi extends React.Component { setParameter={this.setParameter} replaceTemplateStrings={this.props.replaceTemplateStrings} key={index} + isCloudEnabled={this.props.isCloudEnabled} /> ); }); diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 012856ca9213c..0f0d072799061 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -175,6 +175,16 @@ export class ApmPlugin implements Plugin { () => import('./tutorial/tutorial_fleet_instructions') ); + pluginSetupDeps.home?.tutorials.registerCustomComponent( + 'TutorialConfigAgent', + () => import('./tutorial/config_agent') + ); + + pluginSetupDeps.home?.tutorials.registerCustomComponent( + 'TutorialConfigAgentRumScript', + () => import('./tutorial/config_agent/rum_script') + ); + plugins.observability.dashboard.register({ appName: 'apm', hasData: async () => { diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts new file mode 100644 index 0000000000000..97b5f3315bcdb --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const django = `# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.addAgentComment', + { + defaultMessage: 'Add the agent to the installed apps', + } +)} +INSTALLED_APPS = ( +'elasticapm.contrib.django', +# ... +) + +ELASTIC_APM = {curlyOpen} +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } +)} +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } +)} +'SERVICE_NAME': '', + +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } +)} +'SECRET_TOKEN': '{{{secretToken}}}', + +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } +)} +'SERVER_URL': '{{{apmServerUrl}}}', + +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } +)} +'ENVIRONMENT': 'production', +{curlyClose} + +# ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment', + { + defaultMessage: 'To send performance metrics, add our tracing middleware:', + } +)} +MIDDLEWARE = ( +'elasticapm.contrib.django.middleware.TracingMiddleware', +#... +)`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/dotnet.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/dotnet.ts new file mode 100644 index 0000000000000..e083a2b45c716 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/dotnet.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export const dotnet = `{ +"ElasticApm": { +"SecretToken": "{{{secretToken}}}", +"ServerUrls": "{{{apmServerUrl}}}", //Set custom APM Server URL (default: http://localhost:8200) +"ServiceName": "MyApp", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application +"Environment": "production", // Set the service environment +} +}`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts new file mode 100644 index 0000000000000..e4d7fd188e7c6 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const flask = `# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'initialize using environment variables', + } +)} +from elasticapm.contrib.flask import ElasticAPM +app = Flask(__name__) +apm = ElasticAPM(app) + +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.configureElasticApmComment', + { + defaultMessage: + "or configure to use ELASTIC_APM in your application's settings", + } +)} +from elasticapm.contrib.flask import ElasticAPM +app.config['ELASTIC_APM'] = {curlyOpen} +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } +)} +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } +)} +'SERVICE_NAME': '', + +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } +)} +'SECRET_TOKEN': '{{{secretToken}}}', + +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } +)} +'SERVER_URL': '{{{apmServerUrl}}}', + +# ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } +)} +'ENVIRONMENT': 'production', +{curlyClose} + +apm = ElasticAPM(app)`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.test.ts new file mode 100644 index 0000000000000..5dc66e2230524 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.test.ts @@ -0,0 +1,536 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getCommands } from './get_commands'; + +describe('getCommands', () => { + describe('unknown agent', () => { + it('renders empty command', () => { + const commands = getCommands({ + variantId: 'foo', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).toBe(''); + }); + }); + describe('java agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'java', + policyDetails: {}, + }); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls= \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'java', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=localhost:8220 \\\\ + -Delastic.apm.secret_token=foobar \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + }); + describe('RUM(js) agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'js', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "import { init as initApm } from '@elastic/apm-rum' + var apm = initApm({ + + // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space) + serviceName: 'your-app-name', + + // Set custom APM Server URL (default: http://localhost:8200) + serverUrl: '', + + // Set the service version (required for source map feature) + serviceVersion: '', + + // Set the service environment + environment: 'production' + })" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'js', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "import { init as initApm } from '@elastic/apm-rum' + var apm = initApm({ + + // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space) + serviceName: 'your-app-name', + + // Set custom APM Server URL (default: http://localhost:8200) + serverUrl: 'localhost:8220', + + // Set the service version (required for source map feature) + serviceVersion: '', + + // Set the service environment + environment: 'production' + })" + `); + }); + }); + describe('Node.js agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'node', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "// Add this to the VERY top of the first file loaded in your app + var apm = require('elastic-apm-node').start({ + + // Override the service name from package.json + // Allowed characters: a-z, A-Z, 0-9, -, _, and space + serviceName: '', + + // Use if APM Server requires a secret token + secretToken: '', + + // Set the custom APM Server URL (default: http://localhost:8200) + serverUrl: '', + + // Set the service environment + environment: 'production' + })" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'node', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "// Add this to the VERY top of the first file loaded in your app + var apm = require('elastic-apm-node').start({ + + // Override the service name from package.json + // Allowed characters: a-z, A-Z, 0-9, -, _, and space + serviceName: '', + + // Use if APM Server requires a secret token + secretToken: 'foobar', + + // Set the custom APM Server URL (default: http://localhost:8200) + serverUrl: 'localhost:8220', + + // Set the service environment + environment: 'production' + })" + `); + }); + }); + describe('Django agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'django', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Add the agent to the installed apps + INSTALLED_APPS = ( + 'elasticapm.contrib.django', + # ... + ) + + ELASTIC_APM = {curlyOpen} + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + 'SERVICE_NAME': '', + + # Use if APM Server requires a secret token + 'SECRET_TOKEN': '', + + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': '', + + # Set the service environment + 'ENVIRONMENT': 'production', + {curlyClose} + + # To send performance metrics, add our tracing middleware: + MIDDLEWARE = ( + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... + )" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'django', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Add the agent to the installed apps + INSTALLED_APPS = ( + 'elasticapm.contrib.django', + # ... + ) + + ELASTIC_APM = {curlyOpen} + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + 'SERVICE_NAME': '', + + # Use if APM Server requires a secret token + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': 'localhost:8220', + + # Set the service environment + 'ENVIRONMENT': 'production', + {curlyClose} + + # To send performance metrics, add our tracing middleware: + MIDDLEWARE = ( + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... + )" + `); + }); + }); + describe('Flask agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'flask', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# initialize using environment variables + from elasticapm.contrib.flask import ElasticAPM + app = Flask(__name__) + apm = ElasticAPM(app) + + # or configure to use ELASTIC_APM in your application's settings + from elasticapm.contrib.flask import ElasticAPM + app.config['ELASTIC_APM'] = {curlyOpen} + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + 'SERVICE_NAME': '', + + # Use if APM Server requires a secret token + 'SECRET_TOKEN': '', + + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': '', + + # Set the service environment + 'ENVIRONMENT': 'production', + {curlyClose} + + apm = ElasticAPM(app)" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'flask', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# initialize using environment variables + from elasticapm.contrib.flask import ElasticAPM + app = Flask(__name__) + apm = ElasticAPM(app) + + # or configure to use ELASTIC_APM in your application's settings + from elasticapm.contrib.flask import ElasticAPM + app.config['ELASTIC_APM'] = {curlyOpen} + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + 'SERVICE_NAME': '', + + # Use if APM Server requires a secret token + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': 'localhost:8220', + + # Set the service environment + 'ENVIRONMENT': 'production', + {curlyClose} + + apm = ElasticAPM(app)" + `); + }); + }); + describe('Ruby on Rails agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'rails', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space + # Defaults to the name of your Rails app + service_name: 'my-service' + + # Use if APM Server requires a secret token + secret_token: '' + + # Set the custom APM Server URL (default: http://localhost:8200) + server_url: '' + + # Set the service environment + environment: 'production'" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'rails', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space + # Defaults to the name of your Rails app + service_name: 'my-service' + + # Use if APM Server requires a secret token + secret_token: 'foobar' + + # Set the custom APM Server URL (default: http://localhost:8200) + server_url: 'localhost:8220' + + # Set the service environment + environment: 'production'" + `); + }); + }); + describe('Rack agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'rack', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space + # Defaults to the name of your Rack app's class. + service_name: 'my-service' + + # Use if APM Server requires a token + secret_token: '' + + # Set custom APM Server URL (default: http://localhost:8200) + server_url: '', + + # Set the service environment + environment: 'production'" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'rack', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space + # Defaults to the name of your Rack app's class. + service_name: 'my-service' + + # Use if APM Server requires a token + secret_token: 'foobar' + + # Set custom APM Server URL (default: http://localhost:8200) + server_url: 'localhost:8220', + + # Set the service environment + environment: 'production'" + `); + }); + }); + describe('Go agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'go', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables: + + # Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space. + # If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used. + export ELASTIC_APM_SERVICE_NAME= + + # Set custom APM Server URL (default: http://localhost:8200) + export ELASTIC_APM_SERVER_URL= + + # Use if APM Server requires a secret token + export ELASTIC_APM_SECRET_TOKEN= + + # Set the service environment + export ELASTIC_APM_ENVIRONMENT= + " + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'go', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables: + + # Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space. + # If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used. + export ELASTIC_APM_SERVICE_NAME= + + # Set custom APM Server URL (default: http://localhost:8200) + export ELASTIC_APM_SERVER_URL=localhost:8220 + + # Use if APM Server requires a secret token + export ELASTIC_APM_SECRET_TOKEN=foobar + + # Set the service environment + export ELASTIC_APM_ENVIRONMENT= + " + `); + }); + }); + describe('dotNet agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'dotnet', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "{ + \\"ElasticApm\\": { + \\"SecretToken\\": \\"\\", + \\"ServerUrls\\": \\"\\", //Set custom APM Server URL (default: http://localhost:8200) + \\"ServiceName\\": \\"MyApp\\", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application + \\"Environment\\": \\"production\\", // Set the service environment + } + }" + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'dotnet', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "{ + \\"ElasticApm\\": { + \\"SecretToken\\": \\"foobar\\", + \\"ServerUrls\\": \\"localhost:8220\\", //Set custom APM Server URL (default: http://localhost:8200) + \\"ServiceName\\": \\"MyApp\\", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application + \\"Environment\\": \\"production\\", // Set the service environment + } + }" + `); + }); + }); + describe('PHP agent', () => { + it('renders empty commands', () => { + const commands = getCommands({ + variantId: 'php', + policyDetails: {}, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "elastic_apm.server_url=\\"\\" + elastic.apm.secret_token=\\"\\" + elastic_apm.service_name=\\"My service\\" + " + `); + }); + it('renders with secret token and url', () => { + const commands = getCommands({ + variantId: 'php', + policyDetails: { + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }, + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "elastic_apm.server_url=\\"localhost:8220\\" + elastic.apm.secret_token=\\"foobar\\" + elastic_apm.service_name=\\"My service\\" + " + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.ts new file mode 100644 index 0000000000000..73a388c3f735e --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import Mustache from 'mustache'; +import { java } from './java'; +import { node } from './node'; +import { django } from './django'; +import { flask } from './flask'; +import { rails } from './rails'; +import { rack } from './rack'; +import { go } from './go'; +import { dotnet } from './dotnet'; +import { php } from './php'; +import { rum, rumScript } from './rum'; + +const commandsMap: Record = { + java, + node, + django, + flask, + rails, + rack, + go, + dotnet, + php, + js: rum, + js_script: rumScript, +}; + +export function getCommands({ + variantId, + policyDetails, +}: { + variantId: string; + policyDetails: { + apmServerUrl?: string; + secretToken?: string; + }; +}) { + const commands = commandsMap[variantId]; + if (!commands) { + return ''; + } + return Mustache.render(commands, policyDetails); +} diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/go.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/go.ts new file mode 100644 index 0000000000000..a3900420d6fde --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/go.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const go = `# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'Initialize using environment variables:', + } +)} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setServiceNameComment', + { + defaultMessage: + 'Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.', + } +)} +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.usedExecutableNameComment', + { + defaultMessage: + 'If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.', + } +)} +export ELASTIC_APM_SERVICE_NAME= + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } +)} +export ELASTIC_APM_SERVER_URL={{{apmServerUrl}}} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } +)} +export ELASTIC_APM_SECRET_TOKEN={{{secretToken}}} + +# ${i18n.translate( + 'xpack.apm.tutorial.goClient.configure.commands.setServiceEnvironment', + { + defaultMessage: 'Set the service environment', + } +)} +export ELASTIC_APM_ENVIRONMENT= +`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts new file mode 100644 index 0000000000000..249907a9b0c4b --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const java = `java -javaagent:/path/to/elastic-apm-agent-.jar \\ +-Delastic.apm.service_name=my-application \\ +-Delastic.apm.server_urls={{{apmServerUrl}}} \\ +-Delastic.apm.secret_token={{{secretToken}}} \\ +-Delastic.apm.environment=production \\ +-Delastic.apm.application_packages=org.example \\ +-jar my-application.jar`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts new file mode 100644 index 0000000000000..31f9fac0ed480 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; + +export const node = `// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', + { + defaultMessage: + 'Add this to the VERY top of the first file loaded in your app', + } +)} +var apm = require('elastic-apm-node').start({ + +// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Override the service name from package.json', + } +)} +// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', + } +)} +serviceName: '', + +// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } +)} +secretToken: '{{{secretToken}}}', + +// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } +)} +serverUrl: '{{{apmServerUrl}}}', + +// ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } +)} +environment: 'production' +})`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/php.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/php.ts new file mode 100644 index 0000000000000..ea7e8764f89ad --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/php.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const php = `elastic_apm.server_url="{{{apmServerUrl}}}" +elastic.apm.secret_token="{{{secretToken}}}" +elastic_apm.service_name="My service" +`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/rack.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rack.ts new file mode 100644 index 0000000000000..9195ad9f15666 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rack.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const rack = `# config/elastic_apm.yml: + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceNameComment', + { + defaultMessage: + 'Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', + } +)} +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', + { + defaultMessage: "Defaults to the name of your Rack app's class.", + } +)} +service_name: 'my-service' + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a token', + } +)} +secret_token: '{{{secretToken}}}' + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setCustomApmServerComment', + { + defaultMessage: 'Set custom APM Server URL (default: {defaultServerUrl})', + values: { defaultServerUrl: 'http://localhost:8200' }, + } +)} +server_url: '{{{apmServerUrl}}}', + +# ${i18n.translate( + 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceEnvironment', + { + defaultMessage: 'Set the service environment', + } +)} +environment: 'production'`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/rails.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rails.ts new file mode 100644 index 0000000000000..0f8a5508e1ceb --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rails.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const rails = `# config/elastic_apm.yml: + +# Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space +# Defaults to the name of your Rails app +service_name: 'my-service' + +# Use if APM Server requires a secret token +secret_token: '{{{secretToken}}}' + +# Set the custom APM Server URL (default: http://localhost:8200) +server_url: '{{{apmServerUrl}}}' + +# Set the service environment +environment: 'production'`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/rum.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rum.ts new file mode 100644 index 0000000000000..f5de61f64c63a --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/rum.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const rum = `import { init as initApm } from '@elastic/apm-rum' +var apm = initApm({ + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment', + { + defaultMessage: + 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)', + } + )} + serviceName: 'your-app-name', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + serverUrl: '{{{apmServerUrl}}}', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceVersionComment', + { + defaultMessage: + 'Set the service version (required for source map feature)', + } + )} + serviceVersion: '', + + // ${i18n.translate( + 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + environment: 'production' +})`; + +export const rumScript = `\ + + +`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx new file mode 100644 index 0000000000000..33f171ab88247 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Story } from '@storybook/react'; +import { HttpStart } from 'kibana/public'; +import React from 'react'; +import TutorialConfigAgent from './'; +import { APIReturnType } from '../..//services/rest/createCallApmApi'; + +export type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; + +interface Args { + apmAgent: string; + onPrem: boolean; + hasFleetPoliciesWithApmIntegration: boolean; + hasCloudPolicyWithApmIntegration: boolean; +} + +const policyElasticAgentOnCloudAgent: APIResponseType['fleetAgents'][0] = { + id: 'policy-elastic-agent-on-cloud', + name: 'Elastic Cloud agent policy', + apmServerUrl: 'apm_cloud_url', + secretToken: 'apm_cloud_token', +}; + +const fleetAgents: APIResponseType['fleetAgents'] = [ + { + id: '1', + name: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + }, + { + id: '2', + name: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + }, +]; + +function Wrapper({ + hasFleetPoliciesWithApmIntegration, + apmAgent, + onPrem, + hasCloudPolicyWithApmIntegration, +}: Args) { + const http = ({ + get: () => ({ + fleetAgents: [ + ...(hasFleetPoliciesWithApmIntegration ? fleetAgents : []), + ...(hasCloudPolicyWithApmIntegration + ? [policyElasticAgentOnCloudAgent] + : []), + ], + cloudStandaloneSetup: { + apmServerUrl: 'cloud_url', + secretToken: 'foo', + }, + }), + } as unknown) as HttpStart; + return ( + + ); +} +export const Integration: Story = (args) => { + return ; +}; + +Integration.args = { + apmAgent: 'java', + onPrem: true, + hasFleetPoliciesWithApmIntegration: false, + hasCloudPolicyWithApmIntegration: false, +}; + +export default { + title: 'app/Tutorial/AgentConfig', + component: TutorialConfigAgent, + argTypes: { + apmAgent: { + control: { + type: 'select', + options: [ + 'java', + 'node', + 'django', + 'flask', + 'rails', + 'rack', + 'go', + 'dotnet', + 'php', + 'js', + 'js_script', + ], + }, + }, + onPrem: { + control: { type: 'boolean', options: [true, false] }, + }, + hasFleetPoliciesWithApmIntegration: { + control: { type: 'boolean', options: [true, false] }, + }, + hasCloudPolicyWithApmIntegration: { + control: { type: 'boolean', options: [true, false] }, + }, + }, +}; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx new file mode 100644 index 0000000000000..c5261cfc1dc04 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiButton, EuiCopy } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface Props { + commands: string; +} +export function CopyCommands({ commands }: Props) { + return ( + + {(copy) => ( + + {i18n.translate('xpack.apm.tutorial.copySnippet', { + defaultMessage: 'Copy snippet', + })} + + )} + + ); +} diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts new file mode 100644 index 0000000000000..90c9aab80f6f5 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts @@ -0,0 +1,318 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getPolicyOptions } from './get_policy_options'; +import { APIReturnType } from '../../services/rest/createCallApmApi'; + +type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; + +const policyElasticAgentOnCloudAgent = { + id: 'policy-elastic-agent-on-cloud', + name: 'Elastic Cloud agent policy', + apmServerUrl: 'apm_cloud_url', + secretToken: 'apm_cloud_token', +}; + +const fleetAgents = [ + { + id: '1', + name: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + }, + { + id: '2', + name: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + }, +]; + +describe('getPolicyOptions', () => { + describe('running on cloud', () => { + describe('with APM on cloud', () => { + it('shows apm on cloud standalone option', () => { + const data: APIResponseType = { + fleetAgents: [], + cloudStandaloneSetup: { + apmServerUrl: 'cloud_url', + secretToken: 'cloud_token', + }, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + expect(options).toEqual([ + { + key: 'cloud', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'cloud_url', + secretToken: 'cloud_token', + isVisible: true, + isSelected: true, + }, + ]); + }); + it('shows apm on cloud standalone option and fleet agents options', () => { + const data: APIResponseType = { + fleetAgents, + cloudStandaloneSetup: { + apmServerUrl: 'cloud_url', + secretToken: 'cloud_token', + }, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + + expect(options).toEqual([ + { + key: 'cloud', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'cloud_url', + secretToken: 'cloud_token', + isVisible: true, + isSelected: true, + }, + + { + key: '1', + type: 'fleetAgents', + label: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + isVisible: true, + isSelected: false, + }, + { + key: '2', + type: 'fleetAgents', + label: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + isVisible: true, + isSelected: false, + }, + ]); + }); + it('selects policy elastic agent on cloud when available', () => { + const data: APIResponseType = { + fleetAgents: [policyElasticAgentOnCloudAgent, ...fleetAgents], + cloudStandaloneSetup: { + apmServerUrl: 'cloud_url', + secretToken: 'cloud_token', + }, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + + expect(options).toEqual([ + { + key: 'policy-elastic-agent-on-cloud', + type: 'fleetAgents', + label: 'Elastic Cloud agent policy', + apmServerUrl: 'apm_cloud_url', + secretToken: 'apm_cloud_token', + isVisible: true, + isSelected: true, + }, + { + key: '1', + type: 'fleetAgents', + label: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + isVisible: true, + isSelected: false, + }, + { + key: '2', + type: 'fleetAgents', + label: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + isVisible: true, + isSelected: false, + }, + ]); + }); + }); + describe('with APM on prem', () => { + it('shows apm on prem standalone option', () => { + const data: APIResponseType = { + fleetAgents: [], + cloudStandaloneSetup: undefined, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + + expect(options).toEqual([ + { + key: 'onPrem', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'http://localhost:8200', + secretToken: '', + isVisible: true, + isSelected: true, + }, + ]); + }); + it('shows apm on prem standalone option and fleet agents options', () => { + const data: APIResponseType = { + fleetAgents, + cloudStandaloneSetup: undefined, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + expect(options).toEqual([ + { + key: 'onPrem', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'http://localhost:8200', + secretToken: '', + isVisible: true, + isSelected: true, + }, + + { + key: '1', + type: 'fleetAgents', + label: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + isVisible: true, + isSelected: false, + }, + { + key: '2', + type: 'fleetAgents', + label: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + isVisible: true, + isSelected: false, + }, + ]); + }); + it('selects policy elastic agent on cloud when available', () => { + const data: APIResponseType = { + fleetAgents: [policyElasticAgentOnCloudAgent, ...fleetAgents], + cloudStandaloneSetup: undefined, + }; + const options = getPolicyOptions({ + isCloudEnabled: true, + data, + }); + + expect(options).toEqual([ + { + key: 'policy-elastic-agent-on-cloud', + type: 'fleetAgents', + label: 'Elastic Cloud agent policy', + apmServerUrl: 'apm_cloud_url', + secretToken: 'apm_cloud_token', + isVisible: true, + isSelected: true, + }, + { + key: '1', + type: 'fleetAgents', + label: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + isVisible: true, + isSelected: false, + }, + { + key: '2', + type: 'fleetAgents', + label: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + isVisible: true, + isSelected: false, + }, + ]); + }); + }); + }); + describe('Running on prem', () => { + it('shows apm on prem standalone option', () => { + const data: APIResponseType = { + fleetAgents: [], + cloudStandaloneSetup: undefined, + }; + const options = getPolicyOptions({ + isCloudEnabled: false, + data, + }); + + expect(options).toEqual([ + { + key: 'onPrem', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'http://localhost:8200', + secretToken: '', + isVisible: true, + isSelected: true, + }, + ]); + }); + it('shows apm on prem standalone option and fleet agents options', () => { + const data: APIResponseType = { + fleetAgents, + cloudStandaloneSetup: undefined, + }; + const options = getPolicyOptions({ + isCloudEnabled: false, + data, + }); + + expect(options).toEqual([ + { + key: 'onPrem', + type: 'standalone', + label: 'Default Standalone configuration', + apmServerUrl: 'http://localhost:8200', + secretToken: '', + isVisible: true, + isSelected: true, + }, + { + key: '1', + type: 'fleetAgents', + label: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + isVisible: true, + isSelected: false, + }, + { + key: '2', + type: 'fleetAgents', + label: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + isVisible: true, + isSelected: false, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.ts b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.ts new file mode 100644 index 0000000000000..afbdc867d3e0a --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { APIResponseType } from './'; + +const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud'; + +const DEFAULT_STANDALONE_CONFIG_LABEL = i18n.translate( + 'xpack.apm.tutorial.agent_config.defaultStandaloneConfig', + { defaultMessage: 'Default Standalone configuration' } +); + +export type PolicyOption = ReturnType[0]; + +export function getPolicyOptions({ + isCloudEnabled, + data, +}: { + isCloudEnabled: boolean; + data: APIResponseType; +}) { + const isCloudVisible = !!( + isCloudEnabled && data.cloudStandaloneSetup?.apmServerUrl + ); + + const fleetAgentsOptions = data.fleetAgents.map((agent) => { + return { + key: agent.id, + type: 'fleetAgents', + label: agent.name, + apmServerUrl: agent.apmServerUrl, + secretToken: agent.secretToken, + isVisible: true, + isSelected: agent.id === POLICY_ELASTIC_AGENT_ON_CLOUD, + }; + }); + + const hasFleetAgentsSelected = fleetAgentsOptions.some( + ({ isSelected }) => isSelected + ); + + return [ + { + key: 'cloud', + type: 'standalone', + label: DEFAULT_STANDALONE_CONFIG_LABEL, + apmServerUrl: data.cloudStandaloneSetup?.apmServerUrl, + secretToken: data.cloudStandaloneSetup?.secretToken, + isVisible: isCloudVisible && !hasFleetAgentsSelected, + isSelected: !hasFleetAgentsSelected, + }, + { + key: 'onPrem', + type: 'standalone', + label: DEFAULT_STANDALONE_CONFIG_LABEL, + apmServerUrl: 'http://localhost:8200', + secretToken: '', + isVisible: !isCloudVisible && !hasFleetAgentsSelected, + isSelected: !hasFleetAgentsSelected, + }, + ...fleetAgentsOptions, + ].filter(({ isVisible }) => isVisible); +} diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx new file mode 100644 index 0000000000000..8f8afe58506a6 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { fireEvent, render, screen } from '@testing-library/react'; +import { HttpStart } from 'kibana/public'; +import React from 'react'; +import TutorialConfigAgent from './'; + +const policyElasticAgentOnCloudAgent = { + id: 'policy-elastic-agent-on-cloud', + name: 'Elastic Cloud agent policy', + apmServerUrl: 'apm_cloud_url', + secretToken: 'apm_cloud_token', +}; + +const fleetAgents = [ + { + id: '1', + name: 'agent foo', + apmServerUrl: 'foo', + secretToken: 'foo', + }, + { + id: '2', + name: 'agent bar', + apmServerUrl: 'bar', + secretToken: 'bar', + }, +]; + +describe('TutorialConfigAgent', () => { + it('renders loading component while API is being called', () => { + const component = render( + + ); + expect(component.getByTestId('loading')).toBeInTheDocument(); + }); + it('updates commands when a different policy is selected', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + let commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + + fireEvent.click(component.getByTestId('comboBoxToggleListButton')); + fireEvent.click(component.getByText('agent foo')); + commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=foo \\\\ + -Delastic.apm.secret_token=foo \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + describe('running on prem', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + }); + describe('running on cloud', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect(component.getByTestId('policySelector_cloud')).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=cloud_url \\\\ + -Delastic.apm.secret_token=cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('selects policy elastic agent on cloud when available by default', async () => { + const component = render( + + ); + expect( + await screen.findByText('Elastic Cloud agent policy') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_policy-elastic-agent-on-cloud') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=apm_cloud_url \\\\ + -Delastic.apm.secret_token=apm_cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx new file mode 100644 index 0000000000000..755c3eca55868 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { HttpStart } from 'kibana/public'; +import React, { useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; +import { APIReturnType } from '../..//services/rest/createCallApmApi'; +import { getCommands } from './commands/get_commands'; +import { CopyCommands } from './copy_commands'; +import { getPolicyOptions, PolicyOption } from './get_policy_options'; +import { PolicySelector } from './policy_selector'; + +export type APIResponseType = APIReturnType<'GET /api/apm/fleet/agents'>; + +const CentralizedContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const MANAGE_FLEET_POLICIES_LABEL = i18n.translate( + 'xpack.apm.tutorial.agent_config.manageFleetPolicies', + { defaultMessage: 'Manage fleet policies' } +); + +const GET_STARTED_WITH_FLEET_LABEL = i18n.translate( + 'xpack.apm.tutorial.agent_config.getStartedWithFleet', + { defaultMessage: 'Get started with fleet' } +); + +interface Props { + variantId: string; + http: HttpStart; + basePath: string; + isCloudEnabled: boolean; +} + +function TutorialConfigAgent({ + variantId, + http, + basePath, + isCloudEnabled, +}: Props) { + const [data, setData] = useState({ + fleetAgents: [], + cloudStandaloneSetup: undefined, + }); + const [isLoading, setIsLoading] = useState(true); + const [selectedOption, setSelectedOption] = useState(); + + useEffect(() => { + async function fetchData() { + setIsLoading(true); + try { + const response = await http.get('/api/apm/fleet/agents'); + if (response) { + setData(response as APIResponseType); + } + } catch (e) { + console.error('Error while fetching fleet agents.', e); + } + } + fetchData(); + }, [http]); + + // Depending the environment running (onPrem/Cloud) different values must be available and automatically selected + const options = useMemo(() => { + const availableOptions = getPolicyOptions({ + isCloudEnabled, + data, + }); + const defaultSelectedOption = availableOptions.find( + ({ isSelected }) => isSelected + ); + setSelectedOption(defaultSelectedOption); + setIsLoading(false); + return availableOptions; + }, [data, isCloudEnabled]); + + if (isLoading) { + return ( + + + + ); + } + + const commands = getCommands({ + variantId, + policyDetails: { + apmServerUrl: selectedOption?.apmServerUrl, + secretToken: selectedOption?.secretToken, + }, + }); + + const hasFleetAgents = !!data.fleetAgents.length; + const fleetLink = hasFleetAgents + ? { + label: MANAGE_FLEET_POLICIES_LABEL, + href: `${basePath}/app/fleet#/policies`, + } + : { + label: GET_STARTED_WITH_FLEET_LABEL, + href: `${basePath}/app/integrations#/detail/apm-0.3.0/overview`, + }; + + return ( + <> + + + + setSelectedOption(newSelectedOption) + } + fleetLink={fleetLink} + /> + + + + + + + + {commands} + + + ); +} + +// eslint-disable-next-line import/no-default-export +export default TutorialConfigAgent; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx new file mode 100644 index 0000000000000..3a0c6d70db82b --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiComboBox, + EuiComboBoxOptionOption, + EuiFormRow, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { groupBy } from 'lodash'; +import React from 'react'; +import { PolicyOption } from './get_policy_options'; + +interface Props { + options: PolicyOption[]; + selectedOption?: PolicyOption; + onChange: (selectedOption?: PolicyOption) => void; + fleetLink: { + label: string; + href: string; + }; +} + +export function PolicySelector({ + options, + selectedOption, + onChange, + fleetLink, +}: Props) { + const { fleetAgents, standalone } = groupBy(options, 'type'); + + const standaloneComboboxOptions: EuiComboBoxOptionOption[] = + standalone?.map(({ key, label }) => ({ key, label })) || []; + + const fleetAgentsComboboxOptions = fleetAgents?.length + ? [ + { + key: 'fleet_policies', + label: i18n.translate( + 'xpack.apm.tutorial.agent_config.fleetPoliciesLabel', + { defaultMessage: 'Fleet policies' } + ), + options: fleetAgents.map(({ key, label }) => ({ key, label })), + }, + ] + : []; + + return ( + + {fleetLink.label} + + } + helpText={i18n.translate( + 'xpack.apm.tutorial.agent_config.choosePolicy.helper', + { + defaultMessage: + 'Adds the selected policy configuration to the snippet below.', + } + )} + > + { + const newSelectedOption = options.find( + ({ key }) => key === selectedOptions[0].key + ); + onChange(newSelectedOption); + }} + /> + + ); +} diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/rum_script.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/rum_script.tsx new file mode 100644 index 0000000000000..ae3ec803ac588 --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/config_agent/rum_script.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { HttpStart } from 'kibana/public'; +import React from 'react'; +import TutorialConfigAgent from './'; + +interface Props { + http: HttpStart; + basePath: string; + isCloudEnabled: boolean; +} + +function TutorialConfigAgentRumScript({ + http, + basePath, + isCloudEnabled, +}: Props) { + return ( + + ); +} + +// eslint-disable-next-line import/no-default-export +export default TutorialConfigAgentRumScript; diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/tutorial_fleet_instructions.stories.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/tutorial_fleet_instructions.stories.tsx new file mode 100644 index 0000000000000..40b72f06654ff --- /dev/null +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/tutorial_fleet_instructions.stories.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Story } from '@storybook/react'; +import React from 'react'; +import { HttpStart } from 'kibana/public'; +import TutorialFleetInstructions from '.'; + +interface Args { + hasFleetPoliciesWithApmIntegration: boolean; +} + +function Wrapper({ hasFleetPoliciesWithApmIntegration }: Args) { + const http = ({ + get: () => ({ hasData: hasFleetPoliciesWithApmIntegration }), + } as unknown) as HttpStart; + return ( + + ); +} + +export default { + title: 'app/Tutorial/FleetInstructions', + component: TutorialFleetInstructions, + argTypes: { + hasFleetPoliciesWithApmIntegration: { + control: { type: 'boolean', options: [true, false] }, + }, + }, +}; + +export const Instructions: Story = (args) => { + return ; +}; + +Instructions.args = { + hasFleetPoliciesWithApmIntegration: true, +}; diff --git a/x-pack/plugins/apm/server/lib/fleet/get_agents.ts b/x-pack/plugins/apm/server/lib/fleet/get_agents.ts new file mode 100644 index 0000000000000..5ee44bb3ad174 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/get_agents.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CoreSetup, + CoreStart, + SavedObjectsClientContract, +} from 'kibana/server'; +import { APMPluginStartDependencies } from '../../types'; +import { getInternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client'; + +export async function getFleetAgents({ + policyIds, + core, + fleetPluginStart, +}: { + policyIds: string[]; + core: { setup: CoreSetup; start: () => Promise }; + fleetPluginStart: NonNullable; +}) { + // @ts-ignore + const savedObjectsClient: SavedObjectsClientContract = await getInternalSavedObjectsClient( + core.setup + ); + + return await fleetPluginStart.agentPolicyService.getByIds( + savedObjectsClient, + policyIds + ); +} diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 74ca8dc368dad..01323add276df 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -5,23 +5,28 @@ * 2.0. */ +import { keyBy } from 'lodash'; import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; +import { getFleetAgents } from '../lib/fleet/get_agents'; import { getApmPackgePolicies } from '../lib/fleet/get_apm_package_policies'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +const FLEET_REQUIRED_MESSAGE = i18n.translate( + 'xpack.apm.fleet_has_data.fleetRequired', + { + defaultMessage: `Fleet plugin is required`, + } +); + const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /api/apm/fleet/has_data', options: { tags: [] }, handler: async ({ core, plugins }) => { const fleetPluginStart = await plugins.fleet?.start(); if (!fleetPluginStart) { - throw Boom.internal( - i18n.translate('xpack.apm.fleet_has_data.fleetRequired', { - defaultMessage: `Fleet plugin is required`, - }) - ); + throw Boom.internal(FLEET_REQUIRED_MESSAGE); } const packagePolicies = await getApmPackgePolicies({ core, @@ -31,6 +36,54 @@ const hasFleetDataRoute = createApmServerRoute({ }, }); -export const ApmFleetRouteRepository = createApmServerRouteRepository().add( - hasFleetDataRoute -); +const fleetAgentsRoute = createApmServerRoute({ + endpoint: 'GET /api/apm/fleet/agents', + options: { tags: [] }, + handler: async ({ core, plugins }) => { + const cloudSetup = plugins.cloud?.setup; + const cloudStandaloneSetup = cloudSetup + ? { + apmServerUrl: cloudSetup?.apm.url, + secretToken: cloudSetup?.apm.secretToken, + } + : undefined; + + const fleetPluginStart = await plugins.fleet?.start(); + if (!fleetPluginStart) { + throw Boom.internal(FLEET_REQUIRED_MESSAGE); + } + // fetches package policies that contains APM integrations + const packagePolicies = await getApmPackgePolicies({ + core, + fleetPluginStart, + }); + + const policiesGroupedById = keyBy(packagePolicies.items, 'policy_id'); + + // fetches all agents with the found package policies + const fleetAgents = await getFleetAgents({ + policyIds: Object.keys(policiesGroupedById), + core, + fleetPluginStart, + }); + + return { + cloudStandaloneSetup, + fleetAgents: fleetAgents.map((agent) => { + const packagePolicy = policiesGroupedById[agent.id]; + const apmServerCompiledInputs = + packagePolicy.inputs[0].compiled_input['apm-server']; + return { + id: agent.id, + name: agent.name, + apmServerUrl: apmServerCompiledInputs?.url, + secretToken: apmServerCompiledInputs?.secret_token, + }; + }), + }; + }, +}); + +export const ApmFleetRouteRepository = createApmServerRouteRepository() + .add(hasFleetDataRoute) + .add(fleetAgentsRoute); diff --git a/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts index ba11a996f00df..e2bf09dae5bed 100644 --- a/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts +++ b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts @@ -31,55 +31,7 @@ export const createNodeAgentInstructions = ( APM services are created programmatically based on the `serviceName`. \ This agent supports a variety of frameworks but can also be used with your custom stack.', }), - commands: `// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', - { - defaultMessage: - 'Add this to the VERY top of the first file loaded in your app', - } - )} -var apm = require('elastic-apm-node').start({curlyOpen} - - // ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Override the service name from package.json', - } - )} - // ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', - } - )} - serviceName: '', - - // ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - secretToken: '${secretToken}', - - // ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - serverUrl: '${apmServerUrl}', - - // ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - environment: 'production' -{curlyClose})`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.nodeClient.configure.textPost', { @@ -122,70 +74,7 @@ export const createDjangoAgentInstructions = ( APM services are created programmatically based on the `SERVICE_NAME`.', } ), - commands: `# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.addAgentComment', - { - defaultMessage: 'Add the agent to the installed apps', - } - )} -INSTALLED_APPS = ( - 'elasticapm.contrib.django', - # ... -) - -ELASTIC_APM = {curlyOpen} - # ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } - )} - # ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - } - )} - 'SERVICE_NAME': '', - - # ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - 'SECRET_TOKEN': '${secretToken}', - - # ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - 'SERVER_URL': '${apmServerUrl}', - - # ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - 'ENVIRONMENT': 'production', -{curlyClose} - -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment', - { - defaultMessage: - 'To send performance metrics, add our tracing middleware:', - } - )} -MIDDLEWARE = ( - 'elasticapm.contrib.django.middleware.TracingMiddleware', - #... -)`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.djangoClient.configure.textPost', { @@ -225,67 +114,7 @@ export const createFlaskAgentInstructions = ( APM services are created programmatically based on the `SERVICE_NAME`.', } ), - commands: `# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', - { - defaultMessage: 'initialize using environment variables', - } - )} -from elasticapm.contrib.flask import ElasticAPM -app = Flask(__name__) -apm = ElasticAPM(app) - -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.configureElasticApmComment', - { - defaultMessage: - "or configure to use ELASTIC_APM in your application's settings", - } - )} -from elasticapm.contrib.flask import ElasticAPM -app.config['ELASTIC_APM'] = {curlyOpen} - # ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } - )} - # ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - } - )} - 'SERVICE_NAME': '', - - # ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} - 'SECRET_TOKEN': '${secretToken}', - - # ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - 'SERVER_URL': '${apmServerUrl}', - - # ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - 'ENVIRONMENT': 'production', -{curlyClose} - -apm = ElasticAPM(app)`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.flaskClient.configure.textPost', { @@ -325,20 +154,7 @@ export const createRailsAgentInstructions = ( values: { configFile: '`config/elastic_apm.yml`' }, } ), - commands: `# config/elastic_apm.yml: - -# Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space -# Defaults to the name of your Rails app -service_name: 'my-service' - -# Use if APM Server requires a secret token -secret_token: '${secretToken}' - -# Set the custom APM Server URL (default: http://localhost:8200) -server_url: '${apmServerUrl || 'http://localhost:8200'}' - -# Set the service environment -environment: 'production'`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.railsClient.configure.textPost', { @@ -413,48 +229,7 @@ export const createRackAgentInstructions = ( values: { configFile: '`config/elastic_apm.yml`' }, } ), - commands: `# config/elastic_apm.yml: - -# ${i18n.translate( - 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceNameComment', - { - defaultMessage: - 'Set the service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', - } - )} -# ${i18n.translate( - 'xpack.apm.tutorial.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', - { - defaultMessage: "Defaults to the name of your Rack app's class.", - } - )} -service_name: 'my-service' - -# ${i18n.translate( - 'xpack.apm.tutorial.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a token', - } - )} -secret_token: '${secretToken}' - -# ${i18n.translate( - 'xpack.apm.tutorial.rackClient.createConfig.commands.setCustomApmServerComment', - { - defaultMessage: - 'Set custom APM Server URL (default: {defaultServerUrl})', - values: { defaultServerUrl: 'http://localhost:8200' }, - } - )} -server_url: '${apmServerUrl || 'http://localhost:8200'}', - -# ${i18n.translate( - 'xpack.apm.tutorial.rackClient.createConfig.commands.setServiceEnvironment', - { - defaultMessage: 'Set the service environment', - } - )} -environment: 'production'`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.rackClient.createConfig.textPost', { @@ -506,45 +281,7 @@ for details on how to enable RUM support.', The Agent can then be initialized and configured in your application like this:', } ), - commands: `import {curlyOpen} init as initApm {curlyClose} from '@elastic/apm-rum' -var apm = initApm({curlyOpen} - - // ${i18n.translate( - 'xpack.apm.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment', - { - defaultMessage: - 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)', - } - )} - serviceName: 'your-app-name', - - // ${i18n.translate( - 'xpack.apm.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} - serverUrl: '${apmServerUrl}', - - // ${i18n.translate( - 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceVersionComment', - { - defaultMessage: - 'Set the service version (required for source map feature)', - } - )} - serviceVersion: '', - - // ${i18n.translate( - 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } - )} - environment: 'production' -{curlyClose})`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.jsClient.installDependency.textPost', { @@ -575,15 +312,7 @@ and host the file on your Server/CDN before deploying to production.", 'https://unpkg.com/@elastic/apm-rum/dist/bundles/elastic-apm-rum.umd.min.js', }, }), - commands: `\ - - -`.split('\n'), + customComponentName: 'TutorialConfigAgentRumScript', }, ]; @@ -610,55 +339,7 @@ export const createGoAgentInstructions = ( APM services are created programmatically based on the executable \ file name, or the `ELASTIC_APM_SERVICE_NAME` environment variable.', }), - commands: `# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', - { - defaultMessage: 'Initialize using environment variables:', - } - )} - -# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.setServiceNameComment', - { - defaultMessage: - 'Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space.', - } - )} -# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.usedExecutableNameComment', - { - defaultMessage: - 'If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used.', - } - )} -export ELASTIC_APM_SERVICE_NAME= - -# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } - )} -export ELASTIC_APM_SERVER_URL=${apmServerUrl} - -# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } - )} -export ELASTIC_APM_SECRET_TOKEN=${secretToken} - -# ${i18n.translate( - 'xpack.apm.tutorial.goClient.configure.commands.setServiceEnvironment', - { - defaultMessage: 'Set the service environment', - } - )} -export ELASTIC_APM_ENVIRONMENT= -`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate('xpack.apm.tutorial.goClient.configure.textPost', { defaultMessage: 'See the [documentation]({documentationLink}) for advanced configuration.', @@ -743,13 +424,7 @@ Do **not** add the agent as a dependency to your application.', values: { customApmServerUrl: 'http://localhost:8200' }, } ), - commands: `java -javaagent:/path/to/elastic-apm-agent-.jar \\ - -Delastic.apm.service_name=my-application \\ - -Delastic.apm.server_urls=${apmServerUrl || 'http://localhost:8200'} \\ - -Delastic.apm.secret_token=${secretToken} \\ - -Delastic.apm.environment=production \\ - -Delastic.apm.application_packages=org.example \\ - -jar my-application.jar`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.javaClient.startApplication.textPost', { @@ -837,16 +512,7 @@ export const createDotNetAgentInstructions = ( defaultMessage: 'Sample appsettings.json file:', } ), - commands: `{curlyOpen} - "ElasticApm": {curlyOpen} - "SecretToken": "${secretToken}", - "ServerUrls": "${ - apmServerUrl || 'http://localhost:8200' - }", //Set custom APM Server URL (default: http://localhost:8200) - "ServiceName": "MyApp", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application - "Environment": "production", // Set the service environment - {curlyClose} -{curlyClose}`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.dotNetClient.configureAgent.textPost', { @@ -913,12 +579,7 @@ export const createPhpAgentInstructions = ( 'APM is automatically started when your app boots. Configure the agent either via `php.ini` file:', } ), - commands: `elastic_apm.server_url="${ - apmServerUrl || 'http://localhost:8200' - }" -elastic.apm.secret_token="${secretToken}" -elastic_apm.service_name="My service" -`.split('\n'), + customComponentName: 'TutorialConfigAgent', textPost: i18n.translate( 'xpack.apm.tutorial.phpClient.configure.textPost', { diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 43a5a14b425b5..2632b7f9dd85a 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -88,6 +88,7 @@ export const createMockAgentPolicyService = (): jest.Mocked { diff --git a/x-pack/plugins/fleet/server/services/index.ts b/x-pack/plugins/fleet/server/services/index.ts index ebddb695d695b..f82415987e5ac 100644 --- a/x-pack/plugins/fleet/server/services/index.ts +++ b/x-pack/plugins/fleet/server/services/index.ts @@ -69,6 +69,7 @@ export interface AgentPolicyServiceInterface { list: typeof agentPolicyService['list']; getDefaultAgentPolicyId: typeof agentPolicyService['getDefaultAgentPolicyId']; getFullAgentPolicy: typeof agentPolicyService['getFullAgentPolicy']; + getByIds: typeof agentPolicyService['getByIDs']; } // Saved object services From 85709925cc4961a1be723dd8ab7b2ccafb1e44ef Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Tue, 29 Jun 2021 15:00:05 +0200 Subject: [PATCH 60/74] [Security Solutions] Side Navigation phase 2 (#103275) * [SecuritySolutions] [Navigation] Prepare new routing and migrate overview (#101733) * prepare new routing and migrate overview * test fix and todo comments identified * telemetry using app views * navigation groups implemented * cleaning * export subplugin routes as route props array * [Security Solution][Navigation] Migrate Security Solutions 'explore' tab group to deep link navigation (#102306) * Update navigateToApp and getUrlForApp to provide the deepLinkId * Update Hosts and Network routes to start from /hosts and /network * Add Hosts and Network to side nav menu under "Explore" menu group * Delete Hosts and Network old menu code * Fix broken tests * [SecuritySolution] Add detections subplugin to deeplink (#101791) * prepare new routing and migrate overview * init nav deeplink * split detections into rules and alerts * init exception link * init detections * link to rules creation page * link to rules creation page * rename detections to alerts * fix unit tests * fix rules creation page * remove console * fix lint error * fix unit tests * fix unit tests * isolating rules and exceptions page * replace history push with navigateToApp * fix unit test * temporary fix for createCoreStartMock * update cypress * skip failing cypress * skip failing cypress Co-authored-by: semd * Migrate "Investigate" tab group to new side navigation (#102705) * Migrate "Investigate" tab group to new side navigation It includes: * Timelines * Cases * Quick fix useFormatUrl and HeaderPage navigation * [Security Solutions] Management navigation (#102685) * prepare new routing and migrate overview * test fix and todo comments identified * telemetry using app views * navigation groups implemented * cleaning * export subplugin routes as route props array * breadcrumbs changes and sidenav generation improvements * jest tests for breadcrumbs and navigation changes * retrocompatibility for sections that are not yet migrated to deepLinks * management deepLinks and plugin refactoring * home navigation changes * management navigation migrated to deeplinks * jest tests fixed * header page back link improved and tests fixed * type errors fixes * improve home navigation encapsulation Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> * Fix type checking * export header page * fix padding * add redirect routes * unskip detection cypress * fix i18n * fix create your own rules btn * fix cancel button on rules creation page * test fixes * fix breadcrumbs for rules pages * unit test fixes * additional fixes * [Security Solutions] Navigation usage tracker and general changes (#103271) * [Security Solutions] use of currentAppId$ migrated. and some small fixes * unused constants removed * remove unused constant * test fix and types * fix cypress * fix cypress tests * Fix case navTab permission and tests * Revert 'timeline.isOpen' breadcrumb code that was deleted during merge * Fix useInsertTimeline test by removing '/' * change global navigation visible deeplinks * fix /admininstration top level redirect to * fix global search icon, nav order and overview hosts link * update start a new case link * fix rules link in exception list table * unskip cypress tests * update rules link * fix full screen timeline * fixing broken links and administration telemetry split * remove unused comments * remove timeline z-index and cleanup global header component * some minor fixes * add unit tests for detections breadcrumbs * remove case to global/search nav when cases is none * rename test scenario * fix side_panel flyout * fix cases use cases between search/gobal nav * timeline snapshot regenerated and cypres test fixed * rollback management tracking split as it causes unexpected errors on the telemetry component Co-authored-by: Pablo Machado Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Olorunnisola Co-authored-by: Angela Chuang Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> --- .../collectors/application_usage/schema.ts | 9 +- src/plugins/telemetry/schema/oss_plugins.json | 917 ------------------ .../recent_cases/no_cases/index.tsx | 18 +- .../security_solution/common/constants.ts | 56 +- .../integration/cases/attach_timeline.spec.ts | 10 +- .../detection_alerts/alerts_details.spec.ts | 6 +- ..._detection_callouts_index_outdated.spec.ts | 10 +- .../detection_alerts/attach_to_case.spec.ts | 6 +- .../detection_alerts/closing.spec.ts | 4 +- .../detection_alerts/in_progress.spec.ts | 4 +- .../investigate_in_timeline.spec.ts | 4 +- .../missing_privileges_callout.spec.ts | 8 +- .../detection_alerts/opening.spec.ts | 4 +- .../detection_rules/custom_query_rule.spec.ts | 8 +- .../event_correlation_rule.spec.ts | 6 +- .../detection_rules/export_rule.spec.ts | 4 +- .../indicator_match_rule.spec.ts | 10 +- .../machine_learning_rule.spec.ts | 4 +- .../detection_rules/override.spec.ts | 4 +- .../detection_rules/prebuilt_rules.spec.ts | 6 +- .../detection_rules/sorting.spec.ts | 4 +- .../detection_rules/threshold_rule.spec.ts | 4 +- .../exceptions/exceptions_modal.spec.ts | 4 +- .../exceptions/exceptions_table.spec.ts | 18 +- .../integration/exceptions/from_alert.spec.ts | 4 +- .../integration/exceptions/from_rule.spec.ts | 4 +- .../integration/header/navigation.spec.ts | 44 +- .../integration/urls/compatibility.spec.ts | 28 +- .../cypress/integration/urls/state.spec.ts | 2 +- .../value_lists/value_lists.spec.ts | 6 +- .../cypress/screens/exceptions.ts | 5 +- .../cypress/screens/kibana_navigation.ts | 8 +- .../cypress/screens/security_header.ts | 8 +- .../cypress/urls/navigation.ts | 19 +- x-pack/plugins/security_solution/kibana.json | 10 +- .../public/app/deep_links/index.test.ts | 82 ++ .../public/app/deep_links/index.ts | 395 ++++++++ .../public/app/home/global_header/index.tsx | 22 +- .../public/app/home/home_navigations.ts | 130 +++ .../public/app/home/home_navigations.tsx | 72 -- .../public/app/home/index.tsx | 21 +- .../template_wrapper/bottom_bar/index.tsx | 16 +- .../security_solution/public/app/index.tsx | 33 +- .../public/app/search/index.test.ts | 22 - .../public/app/search/index.ts | 240 ----- .../public/app/translations.ts | 41 +- .../security_solution/public/app/types.ts | 25 +- .../cases/components/all_cases/index.tsx | 11 +- .../cases/components/case_view/index.tsx | 20 +- .../cases/components/create/index.test.tsx | 8 +- .../public/cases/components/create/index.tsx | 9 +- .../add_to_case_action.test.tsx | 4 +- .../timeline_actions/add_to_case_action.tsx | 8 +- .../security_solution/public/cases/index.ts | 4 +- .../public/cases/pages/case_details.tsx | 9 +- .../public/cases/pages/configure_cases.tsx | 8 +- .../public/cases/pages/create_case.tsx | 8 +- .../public/cases/pages/index.tsx | 10 +- .../public/cases/pages/utils.ts | 12 +- .../security_solution/public/cases/routes.tsx | 23 +- .../components/endpoint/link_to_app.tsx | 18 +- .../components/endpoint/route_capture.tsx | 9 +- .../components/header_page/index.test.tsx | 12 +- .../common/components/header_page/index.tsx | 18 +- .../components/link_to/__mocks__/index.ts | 2 +- .../public/common/components/link_to/index.ts | 12 +- .../link_to/redirect_to_detection_engine.tsx | 11 +- .../components/link_to/redirect_to_hosts.tsx | 4 +- .../link_to/redirect_to_timelines.tsx | 2 - .../public/common/components/links/index.tsx | 14 +- .../ml_host_conditional_container.tsx | 145 +-- .../ml_network_conditional_container.tsx | 149 +-- .../navigation/breadcrumbs/index.test.ts | 223 ++++- .../navigation/breadcrumbs/index.ts | 33 +- .../components/navigation/index.test.tsx | 121 +-- .../common/components/navigation/index.tsx | 6 +- .../navigation/tab_navigation/index.test.tsx | 218 ++--- .../navigation/tab_navigation/index.tsx | 80 +- .../navigation/tab_navigation/types.ts | 3 - .../common/components/navigation/types.ts | 31 +- .../index.test.tsx | 114 ++- .../index.tsx | 23 +- .../use_security_solution_navigation/types.ts | 2 +- .../use_navigation_items.tsx | 105 +- .../use_primary_navigation.tsx | 15 +- .../common/components/url_state/constants.ts | 6 +- .../common/components/url_state/helpers.ts | 8 +- .../components/url_state/index.test.tsx | 4 +- .../url_state/initialize_redux_by_url.tsx | 2 +- .../common/components/url_state/types.ts | 20 +- .../components/url_state/use_url_state.tsx | 2 +- .../public/common/containers/source/index.tsx | 5 +- .../use_navigate_to_app_event_handler.ts | 6 +- .../common/lib/kibana/__mocks__/index.ts | 9 + .../public/common/lib/kibana/hooks.ts | 56 +- .../mock/endpoint/app_context_render.tsx | 49 +- .../alerts_histogram_panel/index.test.tsx | 7 +- .../alerts_histogram_panel/index.tsx | 5 +- .../load_empty_prompt.test.tsx | 6 +- .../pre_packaged_rules/load_empty_prompt.tsx | 11 +- .../rule_actions_overflow/index.test.tsx | 29 +- .../rules/rule_actions_overflow/index.tsx | 6 +- .../rules/step_define_rule/index.tsx | 2 - .../public/detections/index.ts | 6 +- .../detection_engine/detection_engine.tsx | 15 +- .../pages/detection_engine/index.test.tsx | 20 - .../pages/detection_engine/index.tsx | 37 - .../detection_engine/rules/all/actions.tsx | 14 +- .../rules/all/columns.test.tsx | 7 +- .../detection_engine/rules/all/columns.tsx | 15 +- .../rules/all/exceptions/columns.tsx | 7 +- .../all/exceptions/exceptions_search_bar.tsx | 1 + .../all/exceptions/exceptions_table.test.tsx | 49 +- .../rules/all/exceptions/exceptions_table.tsx | 671 ++++++------- .../detection_engine/rules/all/index.test.tsx | 29 - .../detection_engine/rules/all/index.tsx | 51 +- .../rules/all/rules_tables.tsx | 3 + .../rules/create/index.test.tsx | 2 +- .../detection_engine/rules/create/index.tsx | 31 +- .../detection_engine/rules/details/index.tsx | 24 +- .../rules/edit/index.test.tsx | 1 + .../detection_engine/rules/edit/index.tsx | 33 +- .../detection_engine/rules/index.test.tsx | 2 + .../pages/detection_engine/rules/index.tsx | 28 +- .../detection_engine/rules/translations.ts | 2 +- .../detection_engine/rules/utils.test.ts | 4 +- .../pages/detection_engine/rules/utils.ts | 46 +- .../pages/detection_engine/translations.ts | 4 +- .../public/detections/routes.tsx | 41 +- .../public/exceptions/index.ts | 25 + .../public/exceptions/routes.tsx | 28 + .../security_solution/public/helpers.ts | 58 +- .../security_solution/public/hosts/index.ts | 4 +- .../hosts/pages/details/details_tabs.test.tsx | 2 +- .../public/hosts/pages/details/nav_tabs.tsx | 18 +- .../public/hosts/pages/details/utils.ts | 7 +- .../public/hosts/pages/hosts_tabs.tsx | 14 +- .../public/hosts/pages/index.tsx | 52 +- .../public/hosts/pages/nav_tabs.tsx | 16 +- .../public/hosts/pages/types.ts | 4 +- .../security_solution/public/hosts/routes.tsx | 20 +- .../public/lazy_application_dependencies.tsx | 1 - .../public/lazy_sub_plugins.tsx | 8 +- .../public/management/common/breadcrumbs.ts | 3 +- .../public/management/common/constants.ts | 15 +- .../public/management/common/routing.test.ts | 32 +- .../components/administration_list_page.tsx | 47 +- .../hooks/use_management_format_url.ts | 19 - .../public/management/index.ts | 4 +- .../components/endpoint_agent_status.test.tsx | 2 +- .../view/components/endpoint_policy_link.tsx | 13 +- .../details/components/actions_menu.test.tsx | 18 +- ...k_to_endpoint_details_flyout_subheader.tsx | 9 +- .../view/details/endpoint_details.tsx | 27 +- .../pages/endpoint_hosts/view/hooks/hooks.ts | 14 +- .../view/hooks/use_endpoint_action_items.tsx | 40 +- .../pages/endpoint_hosts/view/index.test.tsx | 51 +- .../pages/endpoint_hosts/view/index.tsx | 26 +- .../event_filters/store/middleware.test.ts | 2 +- .../pages/event_filters/store/reducer.test.ts | 2 +- .../event_filter_delete_modal.test.tsx | 2 +- .../view/event_filters_list_page.test.tsx | 8 +- .../public/management/pages/index.test.tsx | 12 +- .../public/management/pages/index.tsx | 19 +- .../components/fleet_event_filters_card.tsx | 25 +- .../components/fleet_trusted_apps_card.tsx | 26 +- .../pages/policy/view/policy_details.test.tsx | 5 +- .../pages/policy/view/policy_details.tsx | 9 +- .../view/policy_forms/protections/malware.tsx | 2 +- .../policy_forms/protections/ransomware.tsx | 2 +- .../trusted_apps/store/middleware.test.ts | 26 +- .../pages/trusted_apps/store/reducer.test.ts | 11 +- .../effected_policy_select.tsx | 11 +- .../trusted_apps_grid/index.test.tsx | 4 +- .../trusted_apps_list/index.test.tsx | 4 +- .../view/trusted_apps_page.test.tsx | 16 +- .../public/management/routes.tsx | 25 +- .../security_solution/public/network/index.ts | 4 +- .../public/network/pages/details/utils.ts | 6 +- .../public/network/pages/index.tsx | 42 +- .../network/pages/navigation/nav_tabs.tsx | 16 +- .../pages/navigation/network_routes.tsx | 14 +- .../public/network/pages/navigation/types.ts | 1 - .../public/network/pages/navigation/utils.ts | 5 +- .../public/network/pages/network.tsx | 5 +- .../public/network/pages/types.ts | 1 - .../public/network/routes.tsx | 24 +- .../components/alerts_by_category/index.tsx | 3 +- .../components/endpoint_notice/index.tsx | 9 +- .../components/events_by_dataset/index.tsx | 3 +- .../components/overview_host/index.tsx | 9 +- .../overview_network/index.test.tsx | 6 +- .../components/overview_network/index.tsx | 3 +- .../components/recent_cases/index.tsx | 22 +- .../components/recent_timelines/index.tsx | 4 +- .../public/overview/index.ts | 4 +- .../public/overview/routes.tsx | 21 +- .../security_solution/public/plugin.tsx | 273 ++---- .../security_solution/public/rules/index.ts | 25 + .../security_solution/public/rules/routes.tsx | 56 ++ .../flyout/add_to_case_button/index.test.tsx | 11 +- .../flyout/add_to_case_button/index.tsx | 9 +- .../components/flyout/pane/index.tsx | 24 +- .../open_timeline/use_timeline_types.test.tsx | 16 + .../open_timeline/use_timeline_types.tsx | 23 +- .../__snapshots__/index.test.tsx.snap | 3 + .../timelines/components/side_panel/index.tsx | 1 + .../renderers/formatted_field_helpers.tsx | 8 +- .../public/timelines/index.ts | 4 +- .../public/timelines/pages/index.tsx | 41 +- .../public/timelines/routes.tsx | 27 +- .../plugins/security_solution/public/types.ts | 19 +- .../security_solution/server/plugin.ts | 29 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 215 files changed, 3239 insertions(+), 3571 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/app/deep_links/index.test.ts create mode 100644 x-pack/plugins/security_solution/public/app/deep_links/index.ts create mode 100644 x-pack/plugins/security_solution/public/app/home/home_navigations.ts delete mode 100644 x-pack/plugins/security_solution/public/app/home/home_navigations.tsx delete mode 100644 x-pack/plugins/security_solution/public/app/search/index.test.ts delete mode 100644 x-pack/plugins/security_solution/public/app/search/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx create mode 100644 x-pack/plugins/security_solution/public/exceptions/index.ts create mode 100644 x-pack/plugins/security_solution/public/exceptions/routes.tsx delete mode 100644 x-pack/plugins/security_solution/public/management/components/hooks/use_management_format_url.ts create mode 100644 x-pack/plugins/security_solution/public/rules/index.ts create mode 100644 x-pack/plugins/security_solution/public/rules/routes.tsx diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 1fa7d8e846c9d..65857f02c883d 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -157,14 +157,7 @@ export const applicationUsageSchema = { security_login: commonSchema, security_logout: commonSchema, security_overwritten_session: commonSchema, - securitySolution: commonSchema, // It's a forward app so we'll likely never report it - 'securitySolution:overview': commonSchema, - 'securitySolution:detections': commonSchema, - 'securitySolution:hosts': commonSchema, - 'securitySolution:network': commonSchema, - 'securitySolution:timelines': commonSchema, - 'securitySolution:case': commonSchema, - 'securitySolution:administration': commonSchema, + securitySolution: commonSchema, siem: commonSchema, space_selector: commonSchema, uptime: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 56fc7697a4e07..d11e1cf78c960 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -5280,923 +5280,6 @@ } } }, - "securitySolution:overview": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:detections": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:hosts": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:network": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:timelines": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:case": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:administration": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, "siem": { "properties": { "appId": { diff --git a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx index a5b90943a219a..2d60cc1b223ad 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; -import { EuiLink } from '@elastic/eui'; import * as i18n from '../translations'; +import { useKibana } from '../../../common/lib/kibana'; +import { LinkAnchor } from '../../links'; const NoCasesComponent = ({ createCaseHref, @@ -17,13 +18,22 @@ const NoCasesComponent = ({ createCaseHref: string; hasWritePermissions: boolean; }) => { + const { navigateToUrl } = useKibana().services.application; + const goToCaseCreation = useCallback( + (e) => { + e.preventDefault(); + navigateToUrl(createCaseHref); + }, + [createCaseHref, navigateToUrl] + ); return hasWritePermissions ? ( <> {i18n.NO_CASES} - {` ${i18n.START_A_NEW_CASE}`} + >{` ${i18n.START_A_NEW_CASE}`} {'!'} ) : ( diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index d59d7e7b7da4f..3fb32856a1ef1 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -62,29 +62,57 @@ export const DEFAULT_INDICATOR_SOURCE_PATH = 'threatintel.indicator'; export const INDICATOR_DESTINATION_PATH = 'threat.indicator'; export enum SecurityPageName { - detections = 'detections', overview = 'overview', + detections = 'detections', + alerts = 'alerts', + rules = 'rules', + exceptions = 'exceptions', hosts = 'hosts', network = 'network', timelines = 'timelines', case = 'case', administration = 'administration', + endpoints = 'endpoints', + policies = 'policies', + trustedApps = 'trusted_apps', + eventFilters = 'event_filters', } -/** - * The ID of the cases plugin - */ -export const CASES_APP_ID = `${APP_ID}:${SecurityPageName.case}`; - -export const APP_OVERVIEW_PATH = `${APP_PATH}/overview`; -export const APP_DETECTIONS_PATH = `${APP_PATH}/detections`; -export const APP_HOSTS_PATH = `${APP_PATH}/hosts`; -export const APP_NETWORK_PATH = `${APP_PATH}/network`; -export const APP_TIMELINES_PATH = `${APP_PATH}/timelines`; -export const APP_CASES_PATH = `${APP_PATH}/cases`; -export const APP_MANAGEMENT_PATH = `${APP_PATH}/administration`; +export enum SecurityPageGroupName { + detect = 'detect', + explore = 'explore', + investigate = 'investigate', + manage = 'manage', +} -export const DETECTIONS_SUB_PLUGIN_ID = `${APP_ID}:${SecurityPageName.detections}`; +export const TIMELINES_PATH = '/timelines'; +export const CASES_PATH = '/cases'; +export const OVERVIEW_PATH = '/overview'; +export const DETECTIONS_PATH = '/detections'; +export const ALERTS_PATH = '/alerts'; +export const RULES_PATH = '/rules'; +export const EXCEPTIONS_PATH = '/exceptions'; +export const HOSTS_PATH = '/hosts'; +export const NETWORK_PATH = '/network'; +export const MANAGEMENT_PATH = '/administration'; +export const ENDPOINTS_PATH = `${MANAGEMENT_PATH}/endpoints`; +export const TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/trusted_apps`; +export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters`; + +export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}`; +export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}`; + +export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}`; +export const APP_RULES_PATH = `${APP_PATH}${RULES_PATH}`; +export const APP_EXCEPTIONS_PATH = `${APP_PATH}${EXCEPTIONS_PATH}`; + +export const APP_HOSTS_PATH = `${APP_PATH}${HOSTS_PATH}`; +export const APP_NETWORK_PATH = `${APP_PATH}${NETWORK_PATH}`; +export const APP_TIMELINES_PATH = `${APP_PATH}${TIMELINES_PATH}`; +export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}`; +export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}`; +export const APP_TRUSTED_APPS_PATH = `${APP_PATH}${TRUSTED_APPS_PATH}`; +export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}`; /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const DEFAULT_INDEX_PATTERN = [ diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts index 3f3209b52120e..b8477d5b08280 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts @@ -21,11 +21,13 @@ import { createCase } from '../../tasks/api_calls/cases'; describe('attach timeline to case', () => { context('without cases created', () => { - beforeEach(() => { + beforeEach((done) => { cleanKibana(); - createTimeline(timeline).then((response) => - cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline') - ); + + createTimeline(timeline).then((response) => { + cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline'); + done(); + }); }); it('attach timeline to a new case', function () { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index aeee7077ec9c0..d9ca43339d412 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -20,17 +20,17 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { unmappedRule } from '../../objects/rule'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Alert details with unmapped fields', () => { beforeEach(() => { cleanKibana(); esArchiverLoad('unmapped_fields'); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(unmappedRule); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); expandFirstAlert(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts index 1c6c604b84fbb..fb0b96c977e32 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts @@ -6,7 +6,7 @@ */ import { ROLES } from '../../../common/test'; -import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; import { newRule } from '../../objects/rule'; import { PAGE_TITLE } from '../../screens/common/page'; @@ -37,7 +37,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); + loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.platform_engineer); waitForAlertsIndexToBeCreated(); // After that we can login as a soc manager. @@ -57,7 +57,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr }); context('On Detections home page', () => { beforeEach(() => { - loadPageAsPlatformEngineerUser(DETECTIONS_URL); + loadPageAsPlatformEngineerUser(ALERTS_URL); }); it('We show the need admin primary callout', () => { @@ -107,7 +107,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr }); context('On Detections home page', () => { beforeEach(() => { - loadPageAsPlatformEngineerUser(DETECTIONS_URL); + loadPageAsPlatformEngineerUser(ALERTS_URL); }); it('We show the need admin primary callout', () => { @@ -157,7 +157,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr }); context('On Detections home page', () => { beforeEach(() => { - loadPageAsPlatformEngineerUser(DETECTIONS_URL); + loadPageAsPlatformEngineerUser(ALERTS_URL); }); it('We show the need admin primary callout', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts index 932f1ceac61e8..6cc5d2443e784 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts @@ -15,11 +15,11 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { login, loginAndWaitForPage, waitForPageWithoutDateRange } from '../../tasks/login'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { ATTACH_ALERT_TO_CASE_BUTTON } from '../../screens/alerts_detection_rules'; const loadDetectionsPage = (role: ROLES) => { - waitForPageWithoutDateRange(DETECTIONS_URL, role); + waitForPageWithoutDateRange(ALERTS_URL, role); waitForAlertsToPopulate(); }; @@ -27,7 +27,7 @@ describe('Alerts timeline', () => { before(() => { // First we login as a privileged user to create alerts. cleanKibana(); - loginAndWaitForPage(DETECTIONS_URL, ROLES.platform_engineer); + loginAndWaitForPage(ALERTS_URL, ROLES.platform_engineer); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts index 741f05129f9c4..6ae23733d6434 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/closing.spec.ts @@ -31,12 +31,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { loginAndWaitForPage } from '../../tasks/login'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Closing alerts', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPage(DETECTIONS_URL); + loginAndWaitForPage(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule, '1', '100m', 100); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts index b4f890e4d8dbf..cb8694d5c35af 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/in_progress.spec.ts @@ -28,12 +28,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { loginAndWaitForPage } from '../../tasks/login'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Marking alerts as in-progress', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPage(DETECTIONS_URL); + loginAndWaitForPage(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts index d705cb652d2ea..115118b6762d9 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/investigate_in_timeline.spec.ts @@ -19,12 +19,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { loginAndWaitForPage } from '../../tasks/login'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Alerts timeline', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPage(DETECTIONS_URL); + loginAndWaitForPage(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts index 87a3dc8474876..20a863e742efd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/missing_privileges_callout.spec.ts @@ -6,7 +6,7 @@ */ import { ROLES } from '../../../common/test'; -import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; import { newRule } from '../../objects/rule'; import { PAGE_TITLE } from '../../screens/common/page'; @@ -47,7 +47,7 @@ describe('Detections > Callouts', () => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer); + loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.platform_engineer); waitForAlertsIndexToBeCreated(); // After that we can login as a read-only user. @@ -57,7 +57,7 @@ describe('Detections > Callouts', () => { context('indicating read-only access to resources', () => { context('On Detections home page', () => { beforeEach(() => { - loadPageAsReadOnlyUser(DETECTIONS_URL); + loadPageAsReadOnlyUser(ALERTS_URL); }); it('We show one primary callout', () => { @@ -125,7 +125,7 @@ describe('Detections > Callouts', () => { context('indicating read-write access to resources', () => { context('On Detections home page', () => { beforeEach(() => { - loadPageAsPlatformEngineer(DETECTIONS_URL); + loadPageAsPlatformEngineer(ALERTS_URL); }); it('We show no callout', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts index bc907dccd0a04..6cbc82b93f446 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/opening.spec.ts @@ -29,12 +29,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; import { loginAndWaitForPage } from '../../tasks/login'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Opening alerts', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPage(DETECTIONS_URL); + loginAndWaitForPage(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index 8210c7c6d8b20..5f9175476795c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -110,7 +110,7 @@ import { saveEditedRule, waitForKibana } from '../../tasks/edit_rule'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { activatesRule } from '../../tasks/rule_details'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Custom detection rules creation', () => { const expectedUrls = newRule.referenceUrls.join(''); @@ -133,7 +133,7 @@ describe('Custom detection rules creation', () => { }); it('Creates and activates a new rule', function () { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); @@ -226,7 +226,7 @@ describe('Custom detection rules deletion and edition', () => { context('Deletion', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); goToManageAlertsDetectionRules(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(newRule, 'rule1'); @@ -302,7 +302,7 @@ describe('Custom detection rules deletion and edition', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); goToManageAlertsDetectionRules(); waitForAlertsIndexToBeCreated(); createCustomRuleActivated(existingRule, 'rule1'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts index b38796cca373d..337e2a8ec5033 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/event_correlation_rule.spec.ts @@ -75,7 +75,7 @@ import { } from '../../tasks/create_new_rule'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Detection rules, EQL', () => { const expectedUrls = eqlRule.referenceUrls.join(''); @@ -99,7 +99,7 @@ describe('Detection rules, EQL', () => { }); it('Creates and activates a new EQL rule', function () { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); @@ -194,7 +194,7 @@ describe('Detection rules, sequence EQL', () => { }); it('Creates and activates a new EQL rule with a sequence', function () { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts index cbd37dec13232..1de636010f967 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/export_rule.spec.ts @@ -16,7 +16,7 @@ import { createCustomRule } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Export rules', () => { beforeEach(() => { @@ -25,7 +25,7 @@ describe('Export rules', () => { 'POST', '/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson' ).as('export'); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRule(newRule).as('ruleResponse'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts index bc8cf0137fa83..c2e8a92474814 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts @@ -123,7 +123,7 @@ import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { addsFieldsToTimeline, goBackToAllRulesTable } from '../../tasks/rule_details'; -import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation'; +import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation'; describe('indicator match', () => { describe('Detection rules, Indicator Match', () => { @@ -407,7 +407,7 @@ describe('indicator match', () => { describe('Generating signals', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); }); it('Creates and activates a new Indicator Match rule', () => { @@ -559,7 +559,7 @@ describe('indicator match', () => { cleanKibana(); esArchiverLoad('threat_indicator'); esArchiverLoad('suspicious_source_event'); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); goToManageAlertsDetectionRules(); createCustomIndicatorRule(newThreatIndicatorRule); reload(); @@ -571,7 +571,7 @@ describe('indicator match', () => { }); beforeEach(() => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); goToManageAlertsDetectionRules(); goToRuleDetails(); }); @@ -687,7 +687,7 @@ describe('indicator match', () => { describe('Duplicates the indicator rule', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); goToManageAlertsDetectionRules(); createCustomIndicatorRule(newThreatIndicatorRule); reload(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts index 65dde40bbd76b..2d869b314b67c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/machine_learning_rule.spec.ts @@ -62,7 +62,7 @@ import { } from '../../tasks/create_new_rule'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Detection rules, machine learning', () => { const expectedUrls = machineLearningRule.referenceUrls.join(''); @@ -76,7 +76,7 @@ describe('Detection rules, machine learning', () => { }); it('Creates and activates a new ml rule', () => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts index f9f1ca14c8164..a791cc293c1f0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/override.spec.ts @@ -86,7 +86,7 @@ import { } from '../../tasks/create_new_rule'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Detection rules, override', () => { const expectedUrls = newOverrideRule.referenceUrls.join(''); @@ -108,7 +108,7 @@ describe('Detection rules, override', () => { }); it('Creates and activates a new custom rule with override option', function () { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts index 74e1d082ae410..b259c0f1d9e33 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/prebuilt_rules.spec.ts @@ -34,7 +34,7 @@ import { } from '../../tasks/alerts_detection_rules'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { totalNumberOfPrebuiltRules } from '../../objects/rule'; import { cleanKibana } from '../../tasks/common'; @@ -50,7 +50,7 @@ describe('Alerts rules, prebuilt rules', () => { const expectedNumberOfPages = Math.ceil(totalNumberOfPrebuiltRules / rowsPerPage); const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); waitForRulesTableToBeLoaded(); @@ -72,7 +72,7 @@ describe('Actions with prebuilt rules', () => { const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`; cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); goToManageAlertsDetectionRules(); waitForRulesTableToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts index bf5c281a43e39..7d42ea533a9ae 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/sorting.spec.ts @@ -36,7 +36,7 @@ import { import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { createCustomRule } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../objects/rule'; @@ -44,7 +44,7 @@ import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../ describe('Alerts detection rules', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); createCustomRule(newRule, '1'); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index 0f4095372f92a..ad71d54eb2a7a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -81,7 +81,7 @@ import { } from '../../tasks/create_new_rule'; import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; describe('Detection rules, threshold', () => { const expectedUrls = newThresholdRule.referenceUrls.join(''); @@ -96,7 +96,7 @@ describe('Detection rules, threshold', () => { createTimeline(newThresholdRule.timeline).then((response) => { rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId; }); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts index dee921b0c668a..a4b929f7d8e1d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_modal.spec.ts @@ -31,7 +31,7 @@ import { ADD_EXCEPTIONS_BTN, } from '../../screens/exceptions'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; // NOTE: You might look at these tests and feel they're overkill, @@ -42,7 +42,7 @@ import { cleanKibana } from '../../tasks/common'; describe('Exceptions modal', () => { before(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts index fdc8a268ca368..83277075b35cc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/exceptions_table.spec.ts @@ -14,7 +14,10 @@ import { goToManageAlertsDetectionRules, waitForAlertsIndexToBeCreated } from '. import { createCustomRule } from '../../tasks/api_calls/rules'; import { goToRuleDetails, waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; -import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; +import { + loginAndWaitForPageWithoutDateRange, + waitForPageWithoutDateRange, +} from '../../tasks/login'; import { addsExceptionFromRuleSettings, goBackToAllRulesTable, @@ -22,13 +25,12 @@ import { waitForTheRuleToBeExecuted, } from '../../tasks/rule_details'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL, EXCEPTIONS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; import { deleteExceptionListWithRuleReference, deleteExceptionListWithoutRuleReference, exportExceptionList, - goToExceptionsTable, searchForExceptionList, waitForExceptionsTableToBeLoaded, clearSearchSelection, @@ -42,7 +44,7 @@ import { createExceptionList } from '../../tasks/api_calls/exceptions'; describe('Exceptions Table', () => { before(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule); goToManageAlertsDetectionRules(); @@ -69,7 +71,7 @@ describe('Exceptions Table', () => { }); it('Filters exception lists on search', () => { - goToExceptionsTable(); + waitForPageWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 3 lists`); @@ -110,7 +112,7 @@ describe('Exceptions Table', () => { it('Exports exception list', async function () { cy.intercept(/(\/api\/exception_lists\/_export)/).as('export'); - goToExceptionsTable(); + waitForPageWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); exportExceptionList(); @@ -124,7 +126,7 @@ describe('Exceptions Table', () => { }); it('Deletes exception list without rule reference', () => { - goToExceptionsTable(); + waitForPageWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 3 lists`); @@ -135,7 +137,7 @@ describe('Exceptions Table', () => { }); it('Deletes exception list with rule reference', () => { - goToExceptionsTable(); + waitForPageWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 2 lists`); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts index e36809380df86..4918de7488ddd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts @@ -33,7 +33,7 @@ import { } from '../../tasks/rule_details'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; describe('From alert', () => { @@ -41,7 +41,7 @@ describe('From alert', () => { beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule, 'rule_testing', '10s'); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts index e0d7e5a32edfd..ea8988456d8b3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts @@ -32,14 +32,14 @@ import { } from '../../tasks/rule_details'; import { refreshPage } from '../../tasks/security_header'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; describe('From rule', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { cleanKibana(); - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsIndexToBeCreated(); createCustomRule(newRule, 'rule_testing', '10s'); goToManageAlertsDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts index 3caddb86d6725..2079e8e47d479 100644 --- a/x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/header/navigation.spec.ts @@ -9,7 +9,9 @@ import { CASES, DETECTIONS, HOSTS, - ADMINISTRATION, + ENDPOINTS, + TRUSTED_APPS, + EVENT_FILTERS, NETWORK, OVERVIEW, TIMELINES, @@ -19,11 +21,13 @@ import { loginAndWaitForPage } from '../../tasks/login'; import { navigateFromHeaderTo } from '../../tasks/security_header'; import { - DETECTIONS_URL, + ALERTS_URL, CASES_URL, HOSTS_URL, KIBANA_HOME, - ADMINISTRATION_URL, + ENDPOINTS_URL, + TRUSTED_APPS_URL, + EVENT_FILTERS_URL, NETWORK_URL, OVERVIEW_URL, TIMELINES_URL, @@ -34,9 +38,9 @@ import { } from '../../tasks/kibana_navigation'; import { CASES_PAGE, - DETECTIONS_PAGE, + ALERTS_PAGE, HOSTS_PAGE, - ADMINISTRATION_PAGE, + ENDPOINTS_PAGE, NETWORK_PAGE, OVERVIEW_PAGE, TIMELINES_PAGE, @@ -54,9 +58,9 @@ describe('top-level navigation common to all pages in the Security app', () => { cy.url().should('include', OVERVIEW_URL); }); - it('navigates to the Detections page', () => { + it('navigates to the Alerts page', () => { navigateFromHeaderTo(DETECTIONS); - cy.url().should('include', DETECTIONS_URL); + cy.url().should('include', ALERTS_URL); }); it('navigates to the Hosts page', () => { @@ -79,9 +83,17 @@ describe('top-level navigation common to all pages in the Security app', () => { cy.url().should('include', CASES_URL); }); - it('navigates to the Administration page', () => { - navigateFromHeaderTo(ADMINISTRATION); - cy.url().should('include', ADMINISTRATION_URL); + it('navigates to the Endpoints page', () => { + navigateFromHeaderTo(ENDPOINTS); + cy.url().should('include', ENDPOINTS_URL); + }); + it('navigates to the Trusted Apps page', () => { + navigateFromHeaderTo(TRUSTED_APPS); + cy.url().should('include', TRUSTED_APPS_URL); + }); + it('navigates to the Event Filters page', () => { + navigateFromHeaderTo(EVENT_FILTERS); + cy.url().should('include', EVENT_FILTERS_URL); }); }); @@ -97,9 +109,9 @@ describe('Kibana navigation to all pages in the Security app ', () => { cy.url().should('include', OVERVIEW_URL); }); - it('navigates to the Detections page', () => { - navigateFromKibanaCollapsibleTo(DETECTIONS_PAGE); - cy.url().should('include', DETECTIONS_URL); + it('navigates to the Alerts page', () => { + navigateFromKibanaCollapsibleTo(ALERTS_PAGE); + cy.url().should('include', ALERTS_URL); }); it('navigates to the Hosts page', () => { @@ -122,8 +134,8 @@ describe('Kibana navigation to all pages in the Security app ', () => { cy.url().should('include', CASES_URL); }); - it('navigates to the Administration page', () => { - navigateFromKibanaCollapsibleTo(ADMINISTRATION_PAGE); - cy.url().should('include', ADMINISTRATION_URL); + it('navigates to the Endpoints page', () => { + navigateFromKibanaCollapsibleTo(ENDPOINTS_PAGE); + cy.url().should('include', ENDPOINTS_URL); }); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts index b956eb64a04e5..bbbd6037d3862 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts @@ -7,7 +7,15 @@ import { loginAndWaitForPage, loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS } from '../../urls/navigation'; +import { + ALERTS_URL, + DETECTIONS, + DETECTIONS_RULE_MANAGEMENT_URL, + RULE_CREATION, + SECURITY_DETECTIONS_RULES_CREATION_URL, + SECURITY_DETECTIONS_RULES_URL, + SECURITY_DETECTIONS_URL, +} from '../../urls/navigation'; import { ABSOLUTE_DATE_RANGE } from '../../urls/state'; import { DATE_PICKER_START_DATE_POPOVER_BUTTON, @@ -25,10 +33,24 @@ describe('URL compatibility', () => { cleanKibana(); }); - it('Redirects to Detection alerts from old Detections URL', () => { + it('Redirects to alerts from old siem Detections URL', () => { loginAndWaitForPage(DETECTIONS); + cy.url().should('include', ALERTS_URL); + }); + + it('Redirects to alerts from old Detections URL', () => { + loginAndWaitForPage(SECURITY_DETECTIONS_URL); + cy.url().should('include', ALERTS_URL); + }); + + it('Redirects to rules from old Detections rules URL', () => { + loginAndWaitForPage(SECURITY_DETECTIONS_RULES_URL); + cy.url().should('include', DETECTIONS_RULE_MANAGEMENT_URL); + }); - cy.url().should('include', '/security/detections'); + it('Redirects to rules creation from old Detections rules creation URL', () => { + loginAndWaitForPage(SECURITY_DETECTIONS_RULES_CREATION_URL); + cy.url().should('include', RULE_CREATION); }); it('sets the global start and end dates from the url with timestamps', () => { diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts index 48dc6faf8524e..f2b644e8d054c 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts @@ -214,7 +214,7 @@ describe('url state', () => { cy.get(ANOMALIES_TAB).should( 'have.attr', 'href', - "/app/security/hosts/siem-kibana/anomalies?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:!('auditbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))" + "/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:!('auditbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')" ); cy.get(BREADCRUMBS) .eq(1) diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts index 0f545136d1c07..a7cc412a920c0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists/value_lists.spec.ts @@ -7,7 +7,7 @@ import { ROLES } from '../../../common/test'; import { deleteRoleAndUser, loginAndWaitForPageWithoutDateRange } from '../../tasks/login'; -import { DETECTIONS_URL } from '../../urls/navigation'; +import { ALERTS_URL } from '../../urls/navigation'; import { waitForAlertsPanelToBeLoaded, waitForAlertsIndexToBeCreated, @@ -35,7 +35,7 @@ import { describe('value lists', () => { describe('management modal', () => { beforeEach(() => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); + loginAndWaitForPageWithoutDateRange(ALERTS_URL); waitForAlertsPanelToBeLoaded(); waitForAlertsIndexToBeCreated(); waitForListsIndexToBeCreated(); @@ -228,7 +228,7 @@ describe('value lists', () => { describe('user with restricted access role', () => { beforeEach(() => { - loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.t1_analyst); + loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.t1_analyst); goToManageAlertsDetectionRules(); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts index 2479b76cf1de4..bd6d3b7887206 100644 --- a/x-pack/plugins/security_solution/cypress/screens/exceptions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/exceptions.ts @@ -42,7 +42,7 @@ export const EXCEPTIONS_TABLE_TAB = '[data-test-subj="allRulesTableTab-exception export const EXCEPTIONS_TABLE = '[data-test-subj="exceptions-table"]'; -export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="header-section-supplements"] input'; +export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="exceptionsHeaderSearchInput"]'; export const EXCEPTIONS_TABLE_SHOWING_LISTS = '[data-test-subj="showingExceptionLists"]'; @@ -50,7 +50,8 @@ export const EXCEPTIONS_TABLE_DELETE_BTN = '[data-test-subj="exceptionsTableDele export const EXCEPTIONS_TABLE_EXPORT_BTN = '[data-test-subj="exceptionsTableExportButton"]'; -export const EXCEPTIONS_TABLE_SEARCH_CLEAR = '[data-test-subj="header-section-supplements"] button'; +export const EXCEPTIONS_TABLE_SEARCH_CLEAR = + '[data-test-subj="allExceptionListsPanel"] button.euiFormControlLayoutClearButton'; export const EXCEPTIONS_TABLE_LIST_NAME = '[data-test-subj="exceptionsTableName"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts index 7a63357aabaf0..36b870598eff4 100644 --- a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts +++ b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts @@ -5,8 +5,8 @@ * 2.0. */ -export const DETECTIONS_PAGE = - '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Detections"]'; +export const ALERTS_PAGE = + '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Alerts"]'; export const CASES_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Cases"]'; @@ -14,8 +14,8 @@ export const HOSTS_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution export const KIBANA_NAVIGATION_TOGGLE = '[data-test-subj="toggleNavButton"]'; -export const ADMINISTRATION_PAGE = - '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Administration"]'; +export const ENDPOINTS_PAGE = + '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Endpoints"]'; export const NETWORK_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Network"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/security_header.ts b/x-pack/plugins/security_solution/cypress/screens/security_header.ts index a3d5b714cdb3f..3573d78bfcf8a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/security_header.ts +++ b/x-pack/plugins/security_solution/cypress/screens/security_header.ts @@ -5,7 +5,7 @@ * 2.0. */ -export const DETECTIONS = '[data-test-subj="navigation-detections"]'; +export const DETECTIONS = '[data-test-subj="navigation-alerts"]'; export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a'; @@ -15,7 +15,11 @@ export const HOSTS = '[data-test-subj="navigation-hosts"]'; export const KQL_INPUT = '[data-test-subj="queryInput"]'; -export const ADMINISTRATION = '[data-test-subj="navigation-administration"]'; +export const ENDPOINTS = '[data-test-subj="navigation-endpoints"]'; + +export const TRUSTED_APPS = '[data-test-subj="navigation-trusted_apps"]'; + +export const EVENT_FILTERS = '[data-test-subj="navigation-event_filters"]'; export const NETWORK = '[data-test-subj="navigation-network"]'; diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index 2beed9e8ec0b7..879f16f0b7e0e 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -5,13 +5,18 @@ * 2.0. */ -export const DETECTIONS_URL = 'app/security/detections'; -export const DETECTIONS_RULE_MANAGEMENT_URL = 'app/security/detections/rules'; -export const detectionsRuleDetailsUrl = (ruleId: string) => - `app/security/detections/rules/id/${ruleId}`; +export const ALERTS_URL = 'app/security/alerts'; +export const DETECTIONS_RULE_MANAGEMENT_URL = 'app/security/rules'; +export const detectionsRuleDetailsUrl = (ruleId: string) => `app/security/rules/id/${ruleId}`; export const CASES_URL = '/app/security/cases'; export const DETECTIONS = '/app/siem#/detections'; +export const SECURITY_DETECTIONS_URL = '/app/security/detections'; +export const SECURITY_DETECTIONS_RULES_URL = '/app/security/detections/rules'; +export const SECURITY_DETECTIONS_RULES_CREATION_URL = '/app/security/detections/rules/create'; + +export const EXCEPTIONS_URL = 'app/security/exceptions'; + export const HOSTS_URL = '/app/security/hosts/allHosts'; export const HOSTS_PAGE_TAB_URLS = { allHosts: '/app/security/hosts/allHosts', @@ -21,9 +26,11 @@ export const HOSTS_PAGE_TAB_URLS = { uncommonProcesses: '/app/security/hosts/uncommonProcesses', }; export const KIBANA_HOME = '/app/home#/'; -export const ADMINISTRATION_URL = '/app/security/administration'; +export const ENDPOINTS_URL = '/app/security/administration/endpoints'; +export const TRUSTED_APPS_URL = '/app/security/administration/trusted_apps'; +export const EVENT_FILTERS_URL = '/app/security/administration/event_filters'; export const NETWORK_URL = '/app/security/network'; export const OVERVIEW_URL = '/app/security/overview'; -export const RULE_CREATION = 'app/security/detections/rules/create'; +export const RULE_CREATION = 'app/security/rules/create'; export const TIMELINES_URL = '/app/security/timelines'; export const TIMELINE_TEMPLATES_URL = '/app/security/timelines/template'; diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index f707e650643ec..990756f3da701 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -37,5 +37,13 @@ ], "server": true, "ui": true, - "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml"] + "requiredBundles": [ + "esUiShared", + "fleet", + "kibanaUtils", + "kibanaReact", + "usageCollection", + "lists", + "ml" + ] } diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts new file mode 100644 index 0000000000000..f125218b68c09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { getDeepLinks } from '.'; +import { Capabilities } from '../../../../../../src/core/public'; +import { SecurityPageName } from '../types'; + +describe('public search functions', () => { + it('returns a subset of links for basic license, full set for platinum', () => { + const basicLicense = 'basic'; + const platinumLicense = 'platinum'; + const basicLinks = getDeepLinks(basicLicense); + const platinumLinks = getDeepLinks(platinumLicense); + + basicLinks.forEach((basicLink, index) => { + const platinumLink = platinumLinks[index]; + expect(basicLink.id).toEqual(platinumLink.id); + const platinumDeepLinksCount = platinumLink.deepLinks?.length || 0; + const basicDeepLinksCount = basicLink.deepLinks?.length || 0; + expect(platinumDeepLinksCount).toBeGreaterThanOrEqual(basicDeepLinksCount); + }); + }); + + it('returns case links for basic license with only read_cases capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, ({ + siem: { read_cases: true, crud_cases: false }, + } as unknown) as Capabilities); + + expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeTruthy(); + }); + + it('returns case links with NO deepLinks for basic license with only read_cases capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, ({ + siem: { read_cases: true, crud_cases: false }, + } as unknown) as Capabilities); + + expect( + basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length === 0 + ).toBeTruthy(); + }); + + it('returns case links with deepLinks for basic license with crud_cases capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, ({ + siem: { read_cases: true, crud_cases: true }, + } as unknown) as Capabilities); + + expect( + (basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length ?? 0) > 0 + ).toBeTruthy(); + }); + + it('returns NO case links for basic license with NO read_cases capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, ({ + siem: { read_cases: false, crud_cases: false }, + } as unknown) as Capabilities); + + expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeFalsy(); + }); + + it('returns case links for basic license with undefined capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, undefined); + + expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeTruthy(); + }); + + it('returns case deepLinks for basic license with undefined capabilities', () => { + const basicLicense = 'basic'; + const basicLinks = getDeepLinks(basicLicense, undefined); + + expect( + (basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length ?? 0) > 0 + ).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts new file mode 100644 index 0000000000000..cbaa789d47489 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -0,0 +1,395 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { Subject } from 'rxjs'; + +import { LicenseType } from '../../../../licensing/common/types'; +import { SecurityDeepLinkName, SecurityDeepLinks, SecurityPageName } from '../types'; +import { + AppDeepLink, + ApplicationStart, + AppNavLinkStatus, + AppUpdater, +} from '../../../../../../src/core/public'; +import { + OVERVIEW, + DETECT, + ALERTS, + RULES, + EXCEPTIONS, + HOSTS, + NETWORK, + TIMELINES, + CASE, + ADMINISTRATION, +} from '../translations'; +import { + OVERVIEW_PATH, + ALERTS_PATH, + RULES_PATH, + EXCEPTIONS_PATH, + HOSTS_PATH, + NETWORK_PATH, + TIMELINES_PATH, + CASES_PATH, + ENDPOINTS_PATH, + TRUSTED_APPS_PATH, + EVENT_FILTERS_PATH, +} from '../../../common/constants'; + +export const topDeepLinks: AppDeepLink[] = [ + { + id: SecurityPageName.overview, + title: OVERVIEW, + path: OVERVIEW_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.overview', { + defaultMessage: 'Overview', + }), + ], + order: 9000, + }, + { + id: SecurityPageName.detections, + title: DETECT, + path: ALERTS_PATH, + navLinkStatus: AppNavLinkStatus.hidden, + keywords: [ + i18n.translate('xpack.securitySolution.search.detect', { + defaultMessage: 'Detect', + }), + ], + }, + { + id: SecurityPageName.hosts, + title: HOSTS, + path: HOSTS_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.hosts', { + defaultMessage: 'Hosts', + }), + ], + order: 9002, + }, + { + id: SecurityPageName.network, + title: NETWORK, + path: NETWORK_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.network', { + defaultMessage: 'Network', + }), + ], + order: 9003, + }, + { + id: SecurityPageName.timelines, + title: TIMELINES, + path: TIMELINES_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.timelines', { + defaultMessage: 'Timelines', + }), + ], + order: 9004, + }, + { + id: SecurityPageName.case, + title: CASE, + path: CASES_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.cases', { + defaultMessage: 'Cases', + }), + ], + order: 9005, + }, + { + id: SecurityPageName.administration, + title: ADMINISTRATION, + path: ENDPOINTS_PATH, + navLinkStatus: AppNavLinkStatus.hidden, + keywords: [ + i18n.translate('xpack.securitySolution.search.administration', { + defaultMessage: 'Administration', + }), + ], + }, +]; + +const nestedDeepLinks: SecurityDeepLinks = { + [SecurityPageName.overview]: { + base: [], + }, + [SecurityPageName.detections]: { + base: [ + { + id: SecurityPageName.alerts, + title: ALERTS, + path: ALERTS_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.alerts', { + defaultMessage: 'Alerts', + }), + ], + searchable: true, + order: 9001, + }, + { + id: SecurityPageName.rules, + title: RULES, + path: RULES_PATH, + navLinkStatus: AppNavLinkStatus.hidden, + keywords: [ + i18n.translate('xpack.securitySolution.search.rules', { + defaultMessage: 'Rules', + }), + ], + searchable: true, + }, + { + id: SecurityPageName.exceptions, + title: EXCEPTIONS, + path: EXCEPTIONS_PATH, + navLinkStatus: AppNavLinkStatus.hidden, + keywords: [ + i18n.translate('xpack.securitySolution.search.exceptions', { + defaultMessage: 'Exceptions', + }), + ], + searchable: true, + }, + ], + }, + [SecurityPageName.hosts]: { + base: [ + { + id: 'authentications', + title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { + defaultMessage: 'Authentications', + }), + path: '/authentications', + }, + { + id: 'uncommonProcesses', + title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { + defaultMessage: 'Uncommon Processes', + }), + path: '/uncommonProcesses', + }, + { + id: 'events', + title: i18n.translate('xpack.securitySolution.search.hosts.events', { + defaultMessage: 'Events', + }), + path: '/events', + }, + { + id: 'externalAlerts', + title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + [SecurityPageName.network]: { + base: [ + { + id: 'dns', + title: i18n.translate('xpack.securitySolution.search.network.dns', { + defaultMessage: 'DNS', + }), + path: '/dns', + }, + { + id: 'http', + title: i18n.translate('xpack.securitySolution.search.network.http', { + defaultMessage: 'HTTP', + }), + path: '/http', + }, + { + id: 'tls', + title: i18n.translate('xpack.securitySolution.search.network.tls', { + defaultMessage: 'TLS', + }), + path: '/tls', + }, + { + id: 'externalAlertsNetwork', + title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', { + defaultMessage: 'External Alerts', + }), + path: '/external-alerts', + }, + ], + premium: [ + { + id: 'anomalies', + title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + ], + }, + [SecurityPageName.timelines]: { + base: [ + { + id: 'timelineTemplates', + title: i18n.translate('xpack.securitySolution.search.timeline.templates', { + defaultMessage: 'Templates', + }), + path: '/template', + }, + ], + }, + [SecurityPageName.case]: { + base: [ + { + id: 'create', + title: i18n.translate('xpack.securitySolution.search.cases.create', { + defaultMessage: 'Create New Case', + }), + path: '/create', + }, + ], + premium: [ + { + id: 'configure', + title: i18n.translate('xpack.securitySolution.search.cases.configure', { + defaultMessage: 'Configure Cases', + }), + path: '/configure', + }, + ], + }, + [SecurityPageName.administration]: { + base: [ + { + id: SecurityPageName.endpoints, + navLinkStatus: AppNavLinkStatus.visible, + title: i18n.translate('xpack.securitySolution.search.administration.endpoints', { + defaultMessage: 'Endpoints', + }), + order: 9006, + path: ENDPOINTS_PATH, + }, + { + id: SecurityPageName.trustedApps, + title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', { + defaultMessage: 'Trusted Applications', + }), + path: TRUSTED_APPS_PATH, + }, + { + id: SecurityPageName.eventFilters, + title: i18n.translate('xpack.securitySolution.search.administration.eventFilters', { + defaultMessage: 'Event Filters', + }), + path: EVENT_FILTERS_PATH, + }, + ], + }, +}; + +/** + * A function that generates the plugin deepLinks + * @param licenseType optional string for license level, if not provided basic is assumed. + */ +export function getDeepLinks( + licenseType?: LicenseType, + capabilities?: ApplicationStart['capabilities'] +): AppDeepLink[] { + return topDeepLinks + .filter( + (deepLink) => + deepLink.id !== SecurityPageName.case || + capabilities == null || + (deepLink.id === SecurityPageName.case && capabilities.siem.read_cases === true) + ) + .map((deepLink) => { + const deepLinkId = deepLink.id as SecurityDeepLinkName; + const subPluginDeepLinks = nestedDeepLinks[deepLinkId]; + const baseDeepLinks = Array.isArray(subPluginDeepLinks.base) + ? [...subPluginDeepLinks.base] + : []; + if ( + deepLinkId === SecurityPageName.case && + capabilities != null && + capabilities.siem.crud_cases === false + ) { + return { + ...deepLink, + deepLinks: [], + }; + } + if (isPremiumLicense(licenseType) && subPluginDeepLinks?.premium) { + return { + ...deepLink, + deepLinks: [...baseDeepLinks, ...subPluginDeepLinks.premium], + }; + } + return { + ...deepLink, + deepLinks: baseDeepLinks, + }; + }); +} + +export function isPremiumLicense(licenseType?: LicenseType): boolean { + return ( + licenseType === 'gold' || + licenseType === 'platinum' || + licenseType === 'enterprise' || + licenseType === 'trial' + ); +} + +export function updateGlobalNavigation({ + capabilities, + updater$, +}: { + capabilities: ApplicationStart['capabilities']; + updater$: Subject; +}) { + const deepLinks = getDeepLinks(undefined, capabilities); + const updatedDeepLinks = deepLinks.map((link) => { + switch (link.id) { + case 'case': + return { + ...link, + navLinkStatus: capabilities.siem.read_cases + ? AppNavLinkStatus.visible + : AppNavLinkStatus.hidden, + }; + default: + return link; + } + }); + + updater$.next(() => ({ + deepLinks: updatedDeepLinks, + })); +} diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx index 98ff11423ce01..85e41a5cc12ca 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -11,6 +11,7 @@ import { EuiHeaderSectionItem, } from '@elastic/eui'; import React, { useEffect, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; import { createPortalNode, OutPortal, InPortal } from 'react-reverse-portal'; import { i18n } from '@kbn/i18n'; @@ -18,7 +19,8 @@ import { AppMountParameters } from '../../../../../../../src/core/public'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; import { MlPopover } from '../../../common/components/ml_popover/ml_popover'; import { useKibana } from '../../../common/lib/kibana'; -import { ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants'; +import { ADD_DATA_PATH } from '../../../../common/constants'; +import { isDetectionsPath } from '../../../../public/helpers'; const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { defaultMessage: 'Add data', @@ -31,27 +33,29 @@ const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.butt export const GlobalHeader = React.memo( ({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => { const portalNode = useMemo(() => createPortalNode(), []); - const { http } = useKibana().services; + const { + http: { + basePath: { prepend }, + }, + } = useKibana().services; + const { pathname } = useLocation(); useEffect(() => { - let unmount = () => {}; - setHeaderActionMenu((element) => { const mount = toMountPoint(); - unmount = mount(element); - return unmount; + return mount(element); }); return () => { portalNode.unmount(); - unmount(); + setHeaderActionMenu(undefined); }; }, [portalNode, setHeaderActionMenu]); return ( - {window.location.pathname.includes(APP_DETECTIONS_PATH) && ( + {isDetectionsPath(pathname) && ( @@ -61,7 +65,7 @@ export const GlobalHeader = React.memo( {BUTTON_ADD_DATA} diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts new file mode 100644 index 0000000000000..271eea47840dc --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as i18n from '../translations'; +import { SecurityPageName, SecurityPageGroupName } from '../types'; +import { SiemNavTab, NavTabGroups } from '../../common/components/navigation/types'; +import { + APP_OVERVIEW_PATH, + APP_RULES_PATH, + APP_ALERTS_PATH, + APP_EXCEPTIONS_PATH, + APP_HOSTS_PATH, + APP_NETWORK_PATH, + APP_TIMELINES_PATH, + APP_CASES_PATH, + APP_MANAGEMENT_PATH, + APP_ENDPOINTS_PATH, + APP_TRUSTED_APPS_PATH, + APP_EVENT_FILTERS_PATH, +} from '../../../common/constants'; + +export const navTabs: SiemNavTab = { + [SecurityPageName.overview]: { + id: SecurityPageName.overview, + name: i18n.OVERVIEW, + href: APP_OVERVIEW_PATH, + disabled: false, + urlKey: 'overview', + }, + [SecurityPageName.alerts]: { + id: SecurityPageName.alerts, + name: i18n.ALERTS, + href: APP_ALERTS_PATH, + disabled: false, + urlKey: SecurityPageName.alerts, + }, + [SecurityPageName.rules]: { + id: SecurityPageName.rules, + name: i18n.RULES, + href: APP_RULES_PATH, + disabled: false, + urlKey: SecurityPageName.rules, + }, + [SecurityPageName.exceptions]: { + id: SecurityPageName.exceptions, + name: i18n.EXCEPTIONS, + href: APP_EXCEPTIONS_PATH, + disabled: false, + urlKey: SecurityPageName.exceptions, + }, + [SecurityPageName.hosts]: { + id: SecurityPageName.hosts, + name: i18n.HOSTS, + href: APP_HOSTS_PATH, + disabled: false, + urlKey: 'host', + }, + [SecurityPageName.network]: { + id: SecurityPageName.network, + name: i18n.NETWORK, + href: APP_NETWORK_PATH, + disabled: false, + urlKey: 'network', + }, + [SecurityPageName.timelines]: { + id: SecurityPageName.timelines, + name: i18n.TIMELINES, + href: APP_TIMELINES_PATH, + disabled: false, + urlKey: 'timeline', + }, + [SecurityPageName.case]: { + id: SecurityPageName.case, + name: i18n.CASE, + href: APP_CASES_PATH, + disabled: false, + urlKey: 'case', + }, + [SecurityPageName.administration]: { + id: SecurityPageName.administration, + name: i18n.ADMINISTRATION, + href: APP_MANAGEMENT_PATH, + disabled: false, + urlKey: SecurityPageName.administration, + }, + [SecurityPageName.endpoints]: { + id: SecurityPageName.endpoints, + name: i18n.ENDPOINTS, + href: APP_ENDPOINTS_PATH, + disabled: false, + urlKey: SecurityPageName.administration, + }, + [SecurityPageName.trustedApps]: { + id: SecurityPageName.trustedApps, + name: i18n.TRUSTED_APPLICATIONS, + href: APP_TRUSTED_APPS_PATH, + disabled: false, + urlKey: SecurityPageName.administration, + }, + [SecurityPageName.eventFilters]: { + id: SecurityPageName.eventFilters, + name: i18n.EVENT_FILTERS, + href: APP_EVENT_FILTERS_PATH, + disabled: false, + urlKey: SecurityPageName.administration, + }, +}; + +export const navTabGroups: NavTabGroups = { + [SecurityPageGroupName.detect]: { + id: SecurityPageGroupName.detect, + name: i18n.DETECT, + }, + [SecurityPageGroupName.explore]: { + id: SecurityPageGroupName.explore, + name: i18n.EXPLORE, + }, + [SecurityPageGroupName.investigate]: { + id: SecurityPageGroupName.investigate, + name: i18n.INVESTIGATE, + }, + [SecurityPageGroupName.manage]: { + id: SecurityPageGroupName.manage, + name: i18n.MANAGE, + }, +}; diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx deleted file mode 100644 index 8358e2f9377b8..0000000000000 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as i18n from '../translations'; -import { SecurityPageName } from '../types'; -import { SiemNavTab } from '../../common/components/navigation/types'; -import { - APP_OVERVIEW_PATH, - APP_DETECTIONS_PATH, - APP_HOSTS_PATH, - APP_NETWORK_PATH, - APP_TIMELINES_PATH, - APP_CASES_PATH, - APP_MANAGEMENT_PATH, -} from '../../../common/constants'; - -export const navTabs: SiemNavTab = { - [SecurityPageName.overview]: { - id: SecurityPageName.overview, - name: i18n.OVERVIEW, - href: APP_OVERVIEW_PATH, - disabled: false, - urlKey: 'overview', - }, - [SecurityPageName.detections]: { - id: SecurityPageName.detections, - name: i18n.DETECTION_ENGINE, - href: APP_DETECTIONS_PATH, - disabled: false, - urlKey: 'detections', - }, - [SecurityPageName.hosts]: { - id: SecurityPageName.hosts, - name: i18n.HOSTS, - href: APP_HOSTS_PATH, - disabled: false, - urlKey: 'host', - }, - [SecurityPageName.network]: { - id: SecurityPageName.network, - name: i18n.NETWORK, - href: APP_NETWORK_PATH, - disabled: false, - urlKey: 'network', - }, - - [SecurityPageName.timelines]: { - id: SecurityPageName.timelines, - name: i18n.TIMELINES, - href: APP_TIMELINES_PATH, - disabled: false, - urlKey: 'timeline', - }, - [SecurityPageName.case]: { - id: SecurityPageName.case, - name: i18n.CASE, - href: APP_CASES_PATH, - disabled: false, - urlKey: 'case', - }, - [SecurityPageName.administration]: { - id: SecurityPageName.administration, - name: i18n.ADMINISTRATION, - href: APP_MANAGEMENT_PATH, - disabled: false, - urlKey: SecurityPageName.administration, - }, -}; diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 17a6fab103d6f..d16c35a832e6b 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { useRef } from 'react'; +import React from 'react'; +import { useLocation } from 'react-router-dom'; import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper'; import { AppLeaveHandler, AppMountParameters } from '../../../../../../src/core/public'; @@ -14,12 +15,11 @@ import { HelpMenu } from '../../common/components/help_menu'; import { UseUrlState } from '../../common/components/url_state'; import { navTabs } from './home_navigations'; import { useInitSourcerer, useSourcererScope } from '../../common/containers/sourcerer'; -import { useKibana } from '../../common/lib/kibana'; -import { DETECTIONS_SUB_PLUGIN_ID } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_security_packages'; import { GlobalHeader } from './global_header'; import { SecuritySolutionTemplateWrapper } from './template_wrapper'; +import { isDetectionsPath } from '../../helpers'; interface HomePageProps { children: React.ReactNode; @@ -32,23 +32,14 @@ const HomePageComponent: React.FC = ({ onAppLeave, setHeaderActionMenu, }) => { - const { application } = useKibana().services; - const subPluginId = useRef(''); - - application.currentAppId$.subscribe((appId) => { - subPluginId.current = appId ?? ''; - }); + const { pathname } = useLocation(); useInitSourcerer( - subPluginId.current === DETECTIONS_SUB_PLUGIN_ID - ? SourcererScopeName.detections - : SourcererScopeName.default + isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default ); const { browserFields, indexPattern } = useSourcererScope( - subPluginId.current === DETECTIONS_SUB_PLUGIN_ID - ? SourcererScopeName.detections - : SourcererScopeName.default + isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default ); // side effect: this will attempt to upgrade the endpoint package if it is not up to date diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx index 08ebbeaee55d4..a2f1ed8c115d6 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/bottom_bar/index.tsx @@ -7,34 +7,28 @@ /* eslint-disable react/display-name */ -import React, { useRef } from 'react'; +import React from 'react'; +import { useLocation } from 'react-router-dom'; import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public'; import { AppLeaveHandler } from '../../../../../../../../src/core/public'; -import { useKibana } from '../../../../common/lib/kibana'; import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; -import { DETECTIONS_SUB_PLUGIN_ID } from '../../../../../common/constants'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { TimelineId } from '../../../../../common/types/timeline'; import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning'; import { Flyout } from '../../../../timelines/components/flyout'; +import { isDetectionsPath } from '../../../../../public/helpers'; export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar'; export const SecuritySolutionBottomBar = React.memo( ({ onAppLeave }: { onAppLeave: (handler: AppLeaveHandler) => void }) => { - const subPluginId = useRef(''); - const { application } = useKibana().services; - application.currentAppId$.subscribe((appId) => { - subPluginId.current = appId ?? ''; - }); + const { pathname } = useLocation(); const [showTimeline] = useShowTimeline(); const { indicesExist } = useSourcererScope( - subPluginId.current === DETECTIONS_SUB_PLUGIN_ID - ? SourcererScopeName.detections - : SourcererScopeName.default + isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default ); return indicesExist && showTimeline ? ( diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 194f119e35478..81437ec9ec6f6 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -7,7 +7,10 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { OVERVIEW_PATH } from '../../common/constants'; +import { NotFoundPage } from '../app/404'; import { SecurityApp } from './app'; import { RenderAppProps } from './types'; @@ -18,8 +21,11 @@ export const renderApp = ({ setHeaderActionMenu, services, store, - SubPluginRoutes, + usageCollection, + subPlugins, }: RenderAppProps): (() => void) => { + const ApplicationUsageTrackingProvider = + usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; render( - + + + {[ + ...subPlugins.overview.routes, + ...subPlugins.alerts.routes, + ...subPlugins.rules.routes, + ...subPlugins.exceptions.routes, + ...subPlugins.hosts.routes, + ...subPlugins.network.routes, + ...subPlugins.timelines.routes, + ...subPlugins.cases.routes, + ...subPlugins.management.routes, + ].map((route, index) => ( + + ))} + + + + + + + + + , element ); diff --git a/x-pack/plugins/security_solution/public/app/search/index.test.ts b/x-pack/plugins/security_solution/public/app/search/index.test.ts deleted file mode 100644 index 328395f9b85c9..0000000000000 --- a/x-pack/plugins/security_solution/public/app/search/index.test.ts +++ /dev/null @@ -1,22 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { getDeepLinksAndKeywords } from '.'; -import { SecurityPageName } from '../../../common/constants'; - -describe('public search functions', () => { - it('returns a subset of links for basic license, full set for platinum', () => { - const basicLicense = 'basic'; - const platinumLicense = 'platinum'; - for (const pageName of Object.values(SecurityPageName)) { - expect.assertions(Object.values(SecurityPageName).length * 2); - const basicLinkCount = getDeepLinksAndKeywords(pageName, basicLicense).deepLinks?.length || 0; - const platinumLinks = getDeepLinksAndKeywords(pageName, platinumLicense); - expect(platinumLinks.deepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount); - expect(platinumLinks.keywords?.length).not.toBe(null); - } - }); -}); diff --git a/x-pack/plugins/security_solution/public/app/search/index.ts b/x-pack/plugins/security_solution/public/app/search/index.ts deleted file mode 100644 index 93d931fc4d137..0000000000000 --- a/x-pack/plugins/security_solution/public/app/search/index.ts +++ /dev/null @@ -1,240 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { Subject } from 'rxjs'; - -import { AppUpdater } from 'src/core/public'; -import { LicenseType } from '../../../../licensing/common/types'; -import { SecuritySubPluginNames, SecurityDeepLinks } from '../types'; -import { App } from '../../../../../../src/core/public'; - -const securityDeepLinks: SecurityDeepLinks = { - detections: { - base: [ - { - id: 'siemDetectionRules', - title: i18n.translate('xpack.securitySolution.search.detections.manage', { - defaultMessage: 'Manage Rules', - }), - keywords: ['rules'], - path: '/rules', - }, - ], - }, - hosts: { - base: [ - { - id: 'authentications', - title: i18n.translate('xpack.securitySolution.search.hosts.authentications', { - defaultMessage: 'Authentications', - }), - path: '/authentications', - }, - { - id: 'uncommonProcesses', - title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', { - defaultMessage: 'Uncommon Processes', - }), - path: '/uncommonProcesses', - }, - { - id: 'events', - title: i18n.translate('xpack.securitySolution.search.hosts.events', { - defaultMessage: 'Events', - }), - path: '/events', - }, - { - id: 'externalAlerts', - title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { - defaultMessage: 'External Alerts', - }), - path: '/alerts', - }, - ], - premium: [ - { - id: 'anomalies', - title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { - defaultMessage: 'Anomalies', - }), - path: '/anomalies', - }, - ], - }, - network: { - base: [ - { - id: 'dns', - title: i18n.translate('xpack.securitySolution.search.network.dns', { - defaultMessage: 'DNS', - }), - path: '/dns', - }, - { - id: 'http', - title: i18n.translate('xpack.securitySolution.search.network.http', { - defaultMessage: 'HTTP', - }), - path: '/http', - }, - { - id: 'tls', - title: i18n.translate('xpack.securitySolution.search.network.tls', { - defaultMessage: 'TLS', - }), - path: '/tls', - }, - { - id: 'externalAlertsNetwork', - title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', { - defaultMessage: 'External Alerts', - }), - path: '/external-alerts', - }, - ], - premium: [ - { - id: 'anomalies', - title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', { - defaultMessage: 'Anomalies', - }), - path: '/anomalies', - }, - ], - }, - timelines: { - base: [ - { - id: 'timelineTemplates', - title: i18n.translate('xpack.securitySolution.search.timeline.templates', { - defaultMessage: 'Templates', - }), - path: '/template', - }, - ], - }, - overview: { - base: [], - }, - case: { - base: [ - { - id: 'create', - title: i18n.translate('xpack.securitySolution.search.cases.create', { - defaultMessage: 'Create New Case', - }), - path: '/create', - }, - ], - premium: [ - { - id: 'configure', - title: i18n.translate('xpack.securitySolution.search.cases.configure', { - defaultMessage: 'Configure Cases', - }), - path: '/configure', - }, - ], - }, - administration: { - base: [ - { - id: 'trustApplications', - title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', { - defaultMessage: 'Trusted Applications', - }), - path: '/trusted_apps', - }, - ], - }, -}; - -const subpluginKeywords: { [key in SecuritySubPluginNames]: string[] } = { - detections: [ - i18n.translate('xpack.securitySolution.search.detections', { - defaultMessage: 'Detections', - }), - ], - hosts: [ - i18n.translate('xpack.securitySolution.search.hosts', { - defaultMessage: 'Hosts', - }), - ], - network: [ - i18n.translate('xpack.securitySolution.search.network', { - defaultMessage: 'Network', - }), - ], - timelines: [ - i18n.translate('xpack.securitySolution.search.timelines', { - defaultMessage: 'Timelines', - }), - ], - overview: [ - i18n.translate('xpack.securitySolution.search.overview', { - defaultMessage: 'Overview', - }), - ], - case: [ - i18n.translate('xpack.securitySolution.search.cases', { - defaultMessage: 'Cases', - }), - ], - administration: [ - i18n.translate('xpack.securitySolution.search.administration', { - defaultMessage: 'Endpoint Administration', - }), - ], -}; - -/** - * A function that generates a subPlugin's meta tag - * @param subPluginName SubPluginName of the app to retrieve meta information for. - * @param licenseType optional string for license level, if not provided basic is assumed. - */ -export function getDeepLinksAndKeywords( - subPluginName: SecuritySubPluginNames, - licenseType?: LicenseType -): Pick { - const baseRoutes = [...securityDeepLinks[subPluginName].base]; - if ( - licenseType === 'gold' || - licenseType === 'platinum' || - licenseType === 'enterprise' || - licenseType === 'trial' - ) { - const premiumRoutes = - securityDeepLinks[subPluginName] && securityDeepLinks[subPluginName].premium; - if (premiumRoutes !== undefined) { - return { - keywords: subpluginKeywords[subPluginName], - deepLinks: [...baseRoutes, ...premiumRoutes], - }; - } - } - return { - keywords: subpluginKeywords[subPluginName], - deepLinks: baseRoutes, - }; -} -/** - * A function that updates a subplugin's meta property as appropriate when license level changes. - * @param subPluginName SubPluginName of the app to register deepLinks for - * @param appUpdater an instance of appUpdater$ observable to update search links when needed. - * @param licenseType A string representing the current license level. - */ -export function registerDeepLinks( - subPluginName: SecuritySubPluginNames, - appUpdater?: Subject, - licenseType?: LicenseType -) { - if (appUpdater !== undefined) { - appUpdater.next(() => ({ ...getDeepLinksAndKeywords(subPluginName, licenseType) })); - } -} diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 80ddc19add3fd..edbab928a5c69 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -19,12 +19,13 @@ export const NETWORK = i18n.translate('xpack.securitySolution.navigation.network defaultMessage: 'Network', }); -export const DETECTION_ENGINE = i18n.translate( - 'xpack.securitySolution.navigation.detectionEngine', - { - defaultMessage: 'Detections', - } -); +export const RULES = i18n.translate('xpack.securitySolution.navigation.rules', { + defaultMessage: 'Rules', +}); + +export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exceptions', { + defaultMessage: 'Exceptions', +}); export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { defaultMessage: 'Alerts', @@ -41,3 +42,31 @@ export const CASE = i18n.translate('xpack.securitySolution.navigation.case', { export const ADMINISTRATION = i18n.translate('xpack.securitySolution.navigation.administration', { defaultMessage: 'Administration', }); +export const ENDPOINTS = i18n.translate('xpack.securitySolution.search.administration.endpoints', { + defaultMessage: 'Endpoints', +}); +export const TRUSTED_APPLICATIONS = i18n.translate( + 'xpack.securitySolution.search.administration.trustedApps', + { + defaultMessage: 'Trusted Applications', + } +); +export const EVENT_FILTERS = i18n.translate( + 'xpack.securitySolution.search.administration.eventFilters', + { + defaultMessage: 'Event Filters', + } +); + +export const DETECT = i18n.translate('xpack.securitySolution.navigation.detect', { + defaultMessage: 'Detect', +}); +export const EXPLORE = i18n.translate('xpack.securitySolution.navigation.explore', { + defaultMessage: 'Explore', +}); +export const INVESTIGATE = i18n.translate('xpack.securitySolution.navigation.investigate', { + defaultMessage: 'Investigate', +}); +export const MANAGE = i18n.translate('xpack.securitySolution.navigation.manage', { + defaultMessage: 'Manage', +}); diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 77d5b99e1c3a3..62a61828830be 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -16,9 +16,10 @@ import { StateFromReducersMapObject, CombinedState, } from 'redux'; - +import { RouteProps } from 'react-router-dom'; import { AppMountParameters, AppDeepLink } from '../../../../../src/core/public'; -import { StartServices } from '../types'; +import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; +import { StartedSubPlugins, StartServices } from '../types'; /** * The React properties used to render `SecurityApp` as well as the `element` to render it into. @@ -26,7 +27,8 @@ import { StartServices } from '../types'; export interface RenderAppProps extends AppMountParameters { services: StartServices; store: Store; - SubPluginRoutes: React.FC; + subPlugins: StartedSubPlugins; + usageCollection?: UsageCollectionSetup; } import { State, SubPluginsInitReducer } from '../common/store'; @@ -34,7 +36,7 @@ import { Immutable } from '../../common/endpoint/types'; import { AppAction } from '../common/store/actions'; import { TimelineState } from '../timelines/store/timeline/types'; import { SecurityPageName } from '../../common/constants'; -export { SecurityPageName } from '../../common/constants'; +export { SecurityPageName, SecurityPageGroupName } from '../../common/constants'; export interface SecuritySubPluginStore { initialState: Record; @@ -42,8 +44,10 @@ export interface SecuritySubPluginStore middleware?: Array>>>; } +export type SecuritySubPluginRoutes = RouteProps[]; + export interface SecuritySubPlugin { - SubPluginRoutes: React.FC; + routes: SecuritySubPluginRoutes; storageTimelines?: Pick; } @@ -55,14 +59,21 @@ export type SecuritySubPluginKeyStore = | 'alertList' | 'management'; -export type SecuritySubPluginNames = keyof typeof SecurityPageName; +export type SecurityDeepLinkName = + | SecurityPageName.overview + | SecurityPageName.detections + | SecurityPageName.hosts + | SecurityPageName.network + | SecurityPageName.timelines + | SecurityPageName.case + | SecurityPageName.administration; interface SecurityDeepLink { base: AppDeepLink[]; premium?: AppDeepLink[]; } -export type SecurityDeepLinks = { [key in SecuritySubPluginNames]: SecurityDeepLink }; +export type SecurityDeepLinks = { [key in SecurityDeepLinkName]: SecurityDeepLink }; /** * Returned by the various 'SecuritySubPlugin' classes from the `start` method. diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index aa84f639c4577..3c788e0553079 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -15,7 +15,7 @@ import { } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; export interface AllCasesNavProps { detailName: string; @@ -36,7 +36,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(urlSearch), }); }, @@ -46,7 +47,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCaseConfigure = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(urlSearch), }); }, @@ -59,7 +61,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, onClick: async ({ detailName, subCaseId, search }: AllCasesNavProps) => { - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index dec2d409b020d..5474fcb47d87e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -23,11 +23,7 @@ import { Case, CaseViewRefreshPropInterface } from '../../../../../cases/common' import { TimelineId } from '../../../../common/types/timeline'; import { SecurityPageName } from '../../../app/types'; import { KibanaServices, useKibana } from '../../../common/lib/kibana'; -import { - APP_ID, - CASES_APP_ID, - DETECTION_ENGINE_QUERY_SIGNALS_URL, -} from '../../../../common/constants'; +import { APP_ID, DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -132,7 +128,7 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = const dispatch = useDispatch(); const { formatUrl, search } = useFormatUrl(SecurityPageName.case); const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl( - SecurityPageName.detections + SecurityPageName.rules ); const allCasesLink = getCaseUrl(search); @@ -190,7 +186,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: allCasesLink, }); }, @@ -201,7 +198,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: caseId }), }); }, @@ -213,7 +211,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(search), }); }, @@ -227,7 +226,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), }); }, diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 0b6e98e1badcc..42579c6fbc0ac 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -16,7 +16,7 @@ import { Create } from '.'; import { useKibana } from '../../../common/lib/kibana'; import { Case } from '../../../../../cases/public/containers/types'; import { basicCase } from '../../../../../cases/public/containers/mock'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID, SecurityPageName } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; jest.mock('../use_insert_timeline'); @@ -71,8 +71,9 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenCalledWith(CASES_APP_ID, { + expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, { path: `?${mockRes}`, + deepLinkId: SecurityPageName.case, }) ); }); @@ -95,8 +96,9 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, CASES_APP_ID, { + expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_ID, { path: `/basic-case-id?${mockRes}`, + deepLinkId: SecurityPageName.case, }) ); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index dfd53ae5cc0b0..5e2b7e27fb1e5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -12,9 +12,10 @@ import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_t import { useKibana } from '../../../common/lib/kibana'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; import { useInsertTimeline } from '../use_insert_timeline'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; +import { SecurityPageName } from '../../../app/types'; export const Create = React.memo(() => { const { @@ -24,14 +25,16 @@ export const Create = React.memo(() => { const search = useGetUrlSearch(navTabs.case); const onSuccess = useCallback( async ({ id }) => - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id, search }), }), [navigateToApp, search] ); const handleSetIsCancel = useCallback( async () => - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }), [navigateToApp, search] diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx index 02047c774ca6f..efcc95c1a7782 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx @@ -15,6 +15,7 @@ import { TestProviders } from '../../../common/mock'; import { AddToCaseAction } from './add_to_case_action'; import { basicCase } from '../../../../../cases/public/containers/mock'; import { Case, SECURITY_SOLUTION_OWNER } from '../../../../../cases/common'; +import { APP_ID, SecurityPageName } from '../../../../common/constants'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/link_to', () => { @@ -177,8 +178,9 @@ describe('AddToCaseAction', () => { .first() .simulate('click'); - expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', { + expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, { path: '/basic-case-id', + deepLinkId: SecurityPageName.case, }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index 7025bff1ce49a..f0f56d2497e88 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { Case, CaseStatuses, StatusAll } from '../../../../../cases/common'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; import { Ecs } from '../../../../common/ecs'; import { SecurityPageName } from '../../../app/types'; import { @@ -80,7 +80,8 @@ const AddToCaseActionComponent: React.FC = ({ const onViewCaseClick = useCallback( (id) => { - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id }), }); }, @@ -134,7 +135,8 @@ const AddToCaseActionComponent: React.FC = ({ const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/cases/index.ts b/x-pack/plugins/security_solution/public/cases/index.ts index a520932b48660..631276a6bf6b4 100644 --- a/x-pack/plugins/security_solution/public/cases/index.ts +++ b/x-pack/plugins/security_solution/public/cases/index.ts @@ -6,14 +6,14 @@ */ import { SecuritySubPlugin } from '../app/types'; -import { CasesRoutes } from './routes'; +import { routes } from './routes'; export class Cases { public setup() {} public start(): SecuritySubPlugin { return { - SubPluginRoutes: CasesRoutes, + routes, }; } } diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx index f6bb27b7b7104..e8680b148f940 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx @@ -16,7 +16,7 @@ import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; import { getCaseUrl } from '../../common/components/link_to'; import { navTabs } from '../../app/home/home_navigations'; import { CaseView } from '../components/case_view'; -import { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; export const CaseDetailsPage = React.memo(() => { const { @@ -31,9 +31,12 @@ export const CaseDetailsPage = React.memo(() => { useEffect(() => { if (userPermissions != null && !userPermissions.read) { - navigateToApp(CASES_APP_ID, { path: getCaseUrl(search) }); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, + path: getCaseUrl(search), + }); } - }, [navigateToApp, userPermissions, search]); + }, [userPermissions, navigateToApp, search]); return caseId != null ? ( <> diff --git a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx index d3f235a5da7dc..13a549b6babc9 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx @@ -18,7 +18,8 @@ import { navTabs } from '../../app/home/home_navigations'; import { CaseHeaderPage } from '../components/case_header_page'; import { WhitePageWrapper, SectionWrapper } from '../components/wrappers'; import * as i18n from './translations'; -import { APP_ID, CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; +import { SiemNavTabKey } from '../../common/components/navigation/types'; const ConfigureCasesPageComponent: React.FC = () => { const { @@ -32,14 +33,15 @@ const ConfigureCasesPageComponent: React.FC = () => { () => ({ href: getCaseUrl(search), text: i18n.BACK_TO_ALL, - pageId: SecurityPageName.case, + pageId: SecurityPageName.case as SiemNavTabKey, }), [search] ); useEffect(() => { if (userPermissions != null && !userPermissions.read) { - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); } diff --git a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx index 6c88c4afb6395..e46e5c2074f05 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx @@ -17,7 +17,8 @@ import { navTabs } from '../../app/home/home_navigations'; import { CaseHeaderPage } from '../components/case_header_page'; import { Create } from '../components/create'; import * as i18n from './translations'; -import { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; +import { SiemNavTabKey } from '../../common/components/navigation/types'; export const CreateCasePage = React.memo(() => { const userPermissions = useGetUserCasesPermissions(); @@ -30,14 +31,15 @@ export const CreateCasePage = React.memo(() => { () => ({ href: getCaseUrl(search), text: i18n.BACK_TO_ALL, - pageId: SecurityPageName.case, + pageId: SecurityPageName.case as SiemNavTabKey, }), [search] ); useEffect(() => { if (userPermissions != null && !userPermissions.crud) { - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); } diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index fca19cf5c70a7..0faff93bb708c 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -14,14 +14,14 @@ import { CasesPage } from './case'; import { CreateCasePage } from './create_case'; import { ConfigureCasesPage } from './configure_cases'; import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; +import { CASES_PATH } from '../../../common/constants'; -const casesPagePath = ''; -const caseDetailsPagePath = `${casesPagePath}/:detailName`; +const caseDetailsPagePath = `${CASES_PATH}/:detailName`; const subCaseDetailsPagePath = `${caseDetailsPagePath}/sub-cases/:subCaseId`; const caseDetailsPagePathWithCommentId = `${caseDetailsPagePath}/:commentId`; const subCaseDetailsPagePathWithCommentId = `${subCaseDetailsPagePath}/:commentId`; -const createCasePagePath = `${casesPagePath}/create`; -const configureCasesPagePath = `${casesPagePath}/configure`; +const createCasePagePath = `${CASES_PATH}/create`; +const configureCasesPagePath = `${CASES_PATH}/configure`; const CaseContainerComponent: React.FC = () => { const userPermissions = useGetUserCasesPermissions(); @@ -63,7 +63,7 @@ const CaseContainerComponent: React.FC = () => { - + diff --git a/x-pack/plugins/security_solution/public/cases/pages/utils.ts b/x-pack/plugins/security_solution/public/cases/pages/utils.ts index 1c848190cbef5..968712009e110 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/utils.ts +++ b/x-pack/plugins/security_solution/public/cases/pages/utils.ts @@ -13,7 +13,8 @@ import { getCaseDetailsUrl, getCreateCaseUrl } from '../../common/components/lin import { RouteSpyState } from '../../common/utils/route/types'; import * as i18n from './translations'; import { GetUrlForApp } from '../../common/components/navigation/types'; -import { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; +import { SecurityPageName } from '../../app/types'; export const getBreadcrumbs = ( params: RouteSpyState, @@ -25,7 +26,8 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: queryParameters, }), }, @@ -35,7 +37,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: i18n.CREATE_BC_TITLE, - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(queryParameters), }), }, @@ -45,7 +48,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.state?.caseTitle ?? '', - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }), }), }, diff --git a/x-pack/plugins/security_solution/public/cases/routes.tsx b/x-pack/plugins/security_solution/public/cases/routes.tsx index c937631e9474f..8ea30e60379ca 100644 --- a/x-pack/plugins/security_solution/public/cases/routes.tsx +++ b/x-pack/plugins/security_solution/public/cases/routes.tsx @@ -6,16 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { CASES_PATH } from '../../common/constants'; import { Case } from './pages'; -import { NotFoundPage } from '../app/404'; -export const CasesRoutes: React.FC = () => ( - - - - - } /> - +export const CasesRoutes = () => ( + + + ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: CASES_PATH, + render: CasesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx index da8ef269352c7..201738d3293b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx @@ -8,10 +8,13 @@ import React, { memo, MouseEventHandler } from 'react'; import { EuiLink, EuiLinkProps, EuiButton, EuiButtonProps } from '@elastic/eui'; import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler'; +import { APP_ID } from '../../../../common/constants'; export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ - appId: string; + appId?: string; + /** optional app deep link id */ + deepLinkId?: string; /** Any app specific path (route) */ appPath?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -26,8 +29,17 @@ export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { * a given app without causing a full browser refresh */ export const LinkToApp = memo( - ({ appId, appPath: path, appState: state, onClick, asButton, children, ...otherProps }) => { - const handleOnClick = useNavigateToAppEventHandler(appId, { path, state, onClick }); + ({ + appId = APP_ID, + deepLinkId, + appPath: path, + appState: state, + onClick, + asButton, + children, + ...otherProps + }) => { + const handleOnClick = useNavigateToAppEventHandler(appId, { deepLinkId, path, state, onClick }); return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx index 7a7c812f48528..a5e0c90402df4 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx @@ -9,7 +9,8 @@ import React, { memo, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { AppLocation } from '../../../../common/endpoint/types'; -import { AppAction } from '../../store/actions'; +import { timelineActions } from '../../../timelines/store/timeline'; +import { TimelineId } from '../../../../../timelines/common'; /** * This component should be used above all routes, but below the Provider. @@ -17,7 +18,11 @@ import { AppAction } from '../../store/actions'; */ export const RouteCapture = memo(({ children }) => { const location: AppLocation = useLocation(); - const dispatch: (action: AppAction) => unknown = useDispatch(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); + }, [dispatch, location.pathname]); useEffect(() => { dispatch({ type: 'userChangedUrl', payload: location }); diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx index 8a1748de582c4..2c42353daee75 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.test.tsx @@ -15,17 +15,7 @@ import { HeaderPage } from './index'; import { useMountAppended } from '../../utils/use_mount_appended'; import { SecurityPageName } from '../../../app/types'; -jest.mock('react-router-dom', () => { - const original = jest.requireActual('react-router-dom'); - - return { - ...original, - useHistory: () => ({ - useHistory: jest.fn(), - }), - }; -}); - +jest.mock('../../lib/kibana'); jest.mock('../link_to'); describe('HeaderPage', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index 75453f2d759fb..dc8e19249b6be 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -13,7 +13,6 @@ import { EuiSpacer, } from '@elastic/eui'; import React, { useCallback } from 'react'; -import { useHistory } from 'react-router-dom'; import styled, { css } from 'styled-components'; import { LinkIcon, LinkIconProps } from '../link_icon'; @@ -24,6 +23,8 @@ import { useFormatUrl } from '../link_to'; import { SecurityPageName } from '../../../app/types'; import { Sourcerer } from '../sourcerer'; import { SourcererScopeName } from '../../store/sourcerer/model'; +import { useKibana } from '../../lib/kibana'; +import { SiemNavTabKey } from '../navigation/types'; interface HeaderProps { border?: boolean; @@ -64,10 +65,10 @@ const HeaderSection = styled(EuiPageHeaderSection)` HeaderSection.displayName = 'HeaderSection'; interface BackOptions { - href: LinkIconProps['href']; text: LinkIconProps['children']; + href?: LinkIconProps['href']; dataTestSubj?: string; - pageId: SecurityPageName; + pageId: SiemNavTabKey; } export interface HeaderPageProps extends HeaderProps { @@ -99,16 +100,18 @@ const HeaderPageComponent: React.FC = ({ titleNode, ...rest }) => { - const history = useHistory(); + const { navigateToUrl } = useKibana().services.application; + const { formatUrl } = useFormatUrl(backOptions?.pageId ?? SecurityPageName.overview); + const backUrl = formatUrl(backOptions?.href ?? ''); const goTo = useCallback( (ev) => { ev.preventDefault(); if (backOptions) { - history.push(backOptions.href ?? ''); + navigateToUrl(backUrl); } }, - [backOptions, history] + [backOptions, navigateToUrl, backUrl] ); return ( <> @@ -119,7 +122,7 @@ const HeaderPageComponent: React.FC = ({ {backOptions.text} @@ -128,7 +131,6 @@ const HeaderPageComponent: React.FC = ({ )} {!backOptions && backComponent && <>{backComponent}} - {titleNode || ( ) => string; -export const useFormatUrl = (page: SecurityPageName) => { +export const useFormatUrl = (page: SiemNavTabKey) => { const { getUrlForApp } = useKibana().services.application; const search = useGetUrlSearch(navTabs[page]); const formatUrl = useCallback<FormatUrl>( @@ -48,12 +48,10 @@ export const useFormatUrl = (page: SecurityPageName) => { ? '' : `?${pathArr[1]}` }`; - return getUrlForApp(`${APP_ID}:${page}`, { - path: formattedPath, - absolute, - }); + return getUrlForApp(APP_ID, { deepLinkId: page, path: formattedPath, absolute }); }, [getUrlForApp, page, search] ); + return { formatUrl, search }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx index 3a9837c605bdd..b66d923cf0a15 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_detection_engine.tsx @@ -9,15 +9,12 @@ import { appendSearch } from './helpers'; export const getDetectionEngineUrl = (search?: string) => `${appendSearch(search)}`; -export const getDetectionEngineTabUrl = (tabPath: string, search?: string) => - `/${tabPath}${appendSearch(search)}`; +export const getRulesUrl = (search?: string) => `${appendSearch(search)}`; -export const getRulesUrl = (search?: string) => `/rules${appendSearch(search)}`; - -export const getCreateRuleUrl = (search?: string) => `/rules/create${appendSearch(search)}`; +export const getCreateRuleUrl = (search?: string) => `/create${appendSearch(search)}`; export const getRuleDetailsUrl = (detailName: string, search?: string) => - `/rules/id/${detailName}${appendSearch(search)}`; + `/id/${detailName}${appendSearch(search)}`; export const getEditRuleUrl = (detailName: string, search?: string) => - `/rules/id/${detailName}/edit${appendSearch(search)}`; + `/id/${detailName}/edit${appendSearch(search)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx index a043aeb195820..62057260b6383 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_hosts.tsx @@ -6,10 +6,10 @@ */ import { HostsTableType } from '../../../hosts/store/model'; - +import { HOSTS_PATH } from '../../../../common/constants'; import { appendSearch } from './helpers'; -export const getHostsUrl = (search?: string) => `${appendSearch(search)}`; +export const getHostsUrl = (search?: string) => `${HOSTS_PATH}${appendSearch(search)}`; export const getTabsOnHostsUrl = (tabName: HostsTableType, search?: string) => `/${tabName}${appendSearch(search)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx index adbc2e3a9b670..ecc14781f7005 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx @@ -9,8 +9,6 @@ import { isEmpty } from 'lodash/fp'; import { TimelineTypeLiteral } from '../../../../common/types/timeline'; import { appendSearch } from './helpers'; -export const getTimelinesUrl = (search?: string) => `${appendSearch(search)}`; - export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) => `/${tabName}${appendSearch(search)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 13888c98c4243..0b6b77aab00e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -20,7 +20,7 @@ import React, { useMemo, useCallback } from 'react'; import { isNil } from 'lodash/fp'; import styled from 'styled-components'; -import { IP_REPUTATION_LINKS_SETTING, APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants'; import { DefaultFieldRendererOverflow, DEFAULT_MORE_MAX_HEIGHT, @@ -71,7 +71,8 @@ const HostDetailsLinkComponent: React.FC<{ const goToHostDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, path: getHostDetailsUrl(encodeURIComponent(hostName), search), }); }, @@ -142,7 +143,8 @@ const NetworkDetailsLinkComponent: React.FC<{ const goToNetworkDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.network}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.network, path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search), }); }, @@ -179,7 +181,8 @@ const CaseDetailsLinkComponent: React.FC<{ const goToCaseDetails = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, @@ -206,7 +209,8 @@ export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ child const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(search), }); }, diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx index 048be383441ec..f92512cec5c72 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_host_conditional_container.tsx @@ -8,95 +8,98 @@ import { parse, stringify } from 'query-string'; import React from 'react'; -import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; + import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers'; import { HostsTableType } from '../../../../hosts/store/model'; - import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public'; - +import { HOSTS_PATH } from '../../../../../common/constants'; interface QueryStringType { '?_g': string; query: string | null; timerange: string | null; } -type MlHostConditionalProps = Partial<RouteComponentProps<{}>> & { url: string }; - -export const MlHostConditionalContainer = React.memo<MlHostConditionalProps>(({ url }) => ( - <Switch> - <Route - strict - exact - path={url} - render={({ location }) => { - const queryStringDecoded = parse(location.search.substring(1), { - sort: false, - }) as Required<QueryStringType>; - - if (queryStringDecoded.query != null) { - queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); - } - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { - sort: false, - encode: false, - }); - return <Redirect to={`?${reEncoded}`} />; - }} - /> - <Route - path={`${url}/:hostName`} - render={({ - location, - match: { - params: { hostName }, - }, - }) => { - const queryStringDecoded = parse(location.search.substring(1), { - sort: false, - }) as Required<QueryStringType>; - - if (queryStringDecoded.query != null) { - queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); - } - if (emptyEntity(hostName)) { - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { +export const MlHostConditionalContainer = React.memo(() => { + const { path } = useRouteMatch(); + return ( + <Switch> + <Route + strict + exact + path={path} + render={({ location }) => { + const queryStringDecoded = parse(location.search.substring(1), { sort: false, - encode: false, - }); + }) as Required<QueryStringType>; - return <Redirect to={`/${HostsTableType.anomalies}?${reEncoded}`} />; - } else if (multipleEntities(hostName)) { - const hosts: string[] = getMultipleEntities(hostName); - queryStringDecoded.query = addEntitiesToKql( - ['host.name'], - hosts, - queryStringDecoded.query || '' - ); + if (queryStringDecoded.query != null) { + queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); + } const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { sort: false, encode: false, }); - - return <Redirect to={`/${HostsTableType.anomalies}?${reEncoded}`} />; - } else { - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + return <Redirect to={`${HOSTS_PATH}?${reEncoded}`} />; + }} + /> + <Route + path={`${path}/:hostName`} + render={({ + location, + match: { + params: { hostName }, + }, + }) => { + const queryStringDecoded = parse(location.search.substring(1), { sort: false, - encode: false, - }); + }) as Required<QueryStringType>; + + if (queryStringDecoded.query != null) { + queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); + } + if (emptyEntity(hostName)) { + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + + return <Redirect to={`${HOSTS_PATH}/${HostsTableType.anomalies}?${reEncoded}`} />; + } else if (multipleEntities(hostName)) { + const hosts: string[] = getMultipleEntities(hostName); + queryStringDecoded.query = addEntitiesToKql( + ['host.name'], + hosts, + queryStringDecoded.query || '' + ); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + + return <Redirect to={`${HOSTS_PATH}/${HostsTableType.anomalies}?${reEncoded}`} />; + } else { + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); - return <Redirect to={`/${hostName}/${HostsTableType.anomalies}?${reEncoded}`} />; - } - }} - /> - <Route - path="/ml-hosts/" - render={({ location: { search = '' } }) => ( - <Redirect from="/ml-hosts/" to={`/ml-hosts${search}`} /> - )} - /> - </Switch> -)); + return ( + <Redirect to={`${HOSTS_PATH}/${hostName}/${HostsTableType.anomalies}?${reEncoded}`} /> + ); + } + }} + /> + <Route + path={`${HOSTS_PATH}/ml-hosts/`} + render={({ location: { search = '' } }) => ( + <Redirect from={`${HOSTS_PATH}/ml-hosts/`} to={`${HOSTS_PATH}/ml-hosts${search}`} /> + )} + /> + </Switch> + ); +}); MlHostConditionalContainer.displayName = 'MlHostConditionalContainer'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx index 81327937e7c4c..a144898b4d95c 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/ml_network_conditional_container.tsx @@ -8,95 +8,102 @@ import { parse, stringify } from 'query-string'; import React from 'react'; -import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; import { emptyEntity, getMultipleEntities, multipleEntities } from './entity_helpers'; import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public'; - +import { NETWORK_PATH } from '../../../../../common/constants'; interface QueryStringType { '?_g': string; query: string | null; timerange: string | null; } -type MlNetworkConditionalProps = Partial<RouteComponentProps<{}>> & { url: string }; - -export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProps>(({ url }) => ( - <Switch> - <Route - strict - exact - path={url} - render={({ location }) => { - const queryStringDecoded = parse(location.search.substring(1), { - sort: false, - }) as Required<QueryStringType>; - - if (queryStringDecoded.query != null) { - queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); - } - - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { - sort: false, - encode: false, - }); - - return <Redirect to={`?${reEncoded}`} />; - }} - /> - <Route - path={`${url}/ip/:ip`} - render={({ - location, - match: { - params: { ip }, - }, - }) => { - const queryStringDecoded = parse(location.search.substring(1), { - sort: false, - }) as Required<QueryStringType>; +export const MlNetworkConditionalContainer = React.memo(() => { + const { path } = useRouteMatch(); + return ( + <Switch> + <Route + strict + exact + path={path} + render={({ location }) => { + const queryStringDecoded = parse(location.search.substring(1), { + sort: false, + }) as Required<QueryStringType>; - if (queryStringDecoded.query != null) { - queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); - } + if (queryStringDecoded.query != null) { + queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); + } - if (emptyEntity(ip)) { const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { sort: false, encode: false, }); - return <Redirect to={`?${reEncoded}`} />; - } else if (multipleEntities(ip)) { - const ips: string[] = getMultipleEntities(ip); - queryStringDecoded.query = addEntitiesToKql( - ['source.ip', 'destination.ip'], - ips, - queryStringDecoded.query || '' - ); - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { - sort: false, - encode: false, - }); - return <Redirect to={`?${reEncoded}`} />; - } else { - const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />; + }} + /> + <Route + path={`${path}/ip/:ip`} + render={({ + location, + match: { + params: { ip }, + }, + }) => { + const queryStringDecoded = parse(location.search.substring(1), { sort: false, - encode: false, - }); - return <Redirect to={`/ip/${ip}?${reEncoded}`} />; - } - }} - /> - <Route - path="/ml-network/" - render={({ location: { search = '' } }) => ( - <Redirect from="/ml-network/" to={`/ml-network${search}`} /> - )} - /> - </Switch> -)); + }) as Required<QueryStringType>; + + if (queryStringDecoded.query != null) { + queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); + } + + if (emptyEntity(ip)) { + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + + return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />; + } else if (multipleEntities(ip)) { + const ips: string[] = getMultipleEntities(ip); + queryStringDecoded.query = addEntitiesToKql( + ['source.ip', 'destination.ip'], + ips, + queryStringDecoded.query || '' + ); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />; + } else { + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return <Redirect to={`${NETWORK_PATH}/ip/${ip}?${reEncoded}`} />; + } + }} + /> + <Route + path={`${NETWORK_PATH}/ml-network/`} + render={({ location: { search = '' } }) => ( + <Redirect + from={`${NETWORK_PATH}/ml-network/`} + to={{ + pathname: `${NETWORK_PATH}/ml-network`, + search, + }} + /> + )} + /> + </Switch> + ); +}); MlNetworkConditionalContainer.displayName = 'MlNetworkConditionalContainer'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index c869df6ad388e..6789d8e1d4524 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -73,6 +73,27 @@ const getMockObject = ( name: 'Timelines', urlKey: 'timeline', }, + alerts: { + disabled: false, + href: '/app/security/alerts', + id: 'alerts', + name: 'Alerts', + urlKey: 'alerts', + }, + exceptions: { + disabled: false, + href: '/app/security/exceptions', + id: 'exceptions', + name: 'Exceptions', + urlKey: 'exceptions', + }, + rules: { + disabled: false, + href: '/app/security/rules', + id: 'rules', + name: 'Rules', + urlKey: 'rules', + }, }, pageName, pathName, @@ -112,8 +133,10 @@ const getMockObject = ( }); // The string returned is different from what getUrlForApp returns, but does not matter for the purposes of this test. -const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) => - `${appId}${options?.path ?? ''}`; +const getUrlForAppMock = ( + appId: string, + options?: { deepLinkId?: string; path?: string; absolute?: boolean } +) => `${appId}${options?.deepLinkId ? `/${options.deepLinkId}` : ''}${options?.path ?? ''}`; describe('Navigation Breadcrumbs', () => { const hostName = 'siem-kibana'; @@ -130,12 +153,12 @@ describe('Navigation Breadcrumbs', () => { ); expect(breadcrumbs).toEqual([ { - href: 'securitySolutionoverview', + href: 'securitySolution/overview', text: 'Security', }, { href: - "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", text: 'Hosts', }, { @@ -151,11 +174,11 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', href: - "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Flows', @@ -170,11 +193,11 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Timelines', href: - "securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -185,16 +208,16 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Hosts', href: - "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); @@ -206,15 +229,15 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', href: - "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv4, - href: `securitySolution:network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution/network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -226,45 +249,152 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', href: - "securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv6, - href: `securitySolution:network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution/network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); }); - test('should return Alerts breadcrumbs when supplied detection pathname', () => { + test('should return Alerts breadcrumbs when supplied alerts pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('alerts', '/alerts', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: 'securitySolution/overview' }, + { + text: 'Alerts', + href: '', + }, + ]); + }); + + test('should return Exceptions breadcrumbs when supplied exceptions pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('exceptions', '/exceptions', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: 'securitySolution/overview' }, + { + text: 'Exceptions', + href: '', + }, + ]); + }); + + test('should return Rules breadcrumbs when supplied rules pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('rules', '/rules', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: 'securitySolution/overview' }, + { + text: 'Rules', + href: + "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + + test('should return Rules breadcrumbs when supplied rules Creation pathname', () => { + const breadcrumbs = getBreadcrumbsForRoute( + getMockObject('rules', '/rules/create', undefined), + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: 'securitySolution/overview' }, + { + text: 'Rules', + href: + "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + { + text: 'Create', + href: + "securitySolution/rules/create?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + ]); + }); + + test('should return Rules breadcrumbs when supplied rules Details pathname', () => { + const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; + const mockRuleName = 'RULE_NAME'; const breadcrumbs = getBreadcrumbsForRoute( - getMockObject('detections', '/', undefined), + { + ...getMockObject('rules', `/rules/id/${mockDetailName}`, undefined), + detailName: mockDetailName, + state: { + ruleName: mockRuleName, + }, + }, getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { - text: 'Detections', + text: 'Rules', href: - "securitySolution:detections?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + { + text: mockRuleName, + href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); + + test('should return Rules breadcrumbs when supplied rules Edit pathname', () => { + const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; + const mockRuleName = 'RULE_NAME'; + const breadcrumbs = getBreadcrumbsForRoute( + { + ...getMockObject('rules', `/rules/id/${mockDetailName}/edit`, undefined), + detailName: mockDetailName, + state: { + ruleName: mockRuleName, + }, + }, + getUrlForAppMock + ); + expect(breadcrumbs).toEqual([ + { text: 'Security', href: 'securitySolution/overview' }, + { + text: 'Rules', + href: + "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + }, + { + text: 'RULE_NAME', + href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + }, + { + text: 'Edit', + href: `securitySolution/rules/id/${mockDetailName}/edit?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + }, + ]); + }); + test('should return Cases breadcrumbs when supplied case pathname', () => { const breadcrumbs = getBreadcrumbsForRoute( getMockObject('case', '/', undefined), getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Cases', href: - "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -281,15 +411,15 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Cases', href: - "securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: sampleCase.name, - href: `securitySolution:case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolution/case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); @@ -299,10 +429,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Administration', - href: 'securitySolution:administration', + href: 'securitySolution/endpoints', }, ]); }); @@ -321,11 +451,11 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolutionoverview' }, + { text: 'Security', href: 'securitySolution/overview' }, { text: 'Timelines', href: - "securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(activeTab:query,graphEventId:GRAPH_EVENT_ID,id:TIMELINE_ID,isOpen:!f)", + "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(activeTab:query,graphEventId:GRAPH_EVENT_ID,id:TIMELINE_ID,isOpen:!f)", }, ]); }); @@ -333,20 +463,35 @@ describe('Navigation Breadcrumbs', () => { describe('setBreadcrumbs()', () => { test('should call chrome breadcrumb service with correct breadcrumbs', () => { - setBreadcrumbs(getMockObject('hosts', '/', hostName), chromeMock, getUrlForAppMock); + const navigateToUrlMock = jest.fn(); + setBreadcrumbs( + getMockObject('hosts', '/', hostName), + chromeMock, + getUrlForAppMock, + navigateToUrlMock + ); expect(setBreadcrumbsMock).toBeCalledWith([ - { text: 'Security', href: 'securitySolutionoverview' }, - { + expect.objectContaining({ + text: 'Security', + href: 'securitySolution/overview', + onClick: expect.any(Function), + }), + expect.objectContaining({ text: 'Hosts', href: - "securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", - }, - { + "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + onClick: expect.any(Function), + }), + expect.objectContaining({ text: 'siem-kibana', href: - "securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + onClick: expect.any(Function), + }), + { + text: 'Authentications', + href: '', }, - { text: 'Authentications', href: '' }, ]); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts index a09945f705c58..4578e16dc5540 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts @@ -28,16 +28,29 @@ import { getAppOverviewUrl } from '../../link_to'; import { TabNavigationProps } from '../tab_navigation/types'; import { getSearch } from '../helpers'; -import { GetUrlForApp, SearchNavTab } from '../types'; +import { GetUrlForApp, NavigateToUrl, SearchNavTab } from '../types'; export const setBreadcrumbs = ( spyState: RouteSpyState & TabNavigationProps, chrome: StartServices['chrome'], - getUrlForApp: GetUrlForApp + getUrlForApp: GetUrlForApp, + navigateToUrl: NavigateToUrl ) => { const breadcrumbs = getBreadcrumbsForRoute(spyState, getUrlForApp); if (breadcrumbs) { - chrome.setBreadcrumbs(breadcrumbs); + chrome.setBreadcrumbs( + breadcrumbs.map((breadcrumb) => ({ + ...breadcrumb, + ...(breadcrumb.href && !breadcrumb.onClick + ? { + onClick: (ev) => { + ev.preventDefault(); + navigateToUrl(breadcrumb.href!); + }, + } + : {}), + })) + ); } }; @@ -53,12 +66,12 @@ const isTimelinesRoutes = (spyState: RouteSpyState): spyState is TimelineRouteSp const isCaseRoutes = (spyState: RouteSpyState): spyState is RouteSpyState => spyState != null && spyState.pageName === SecurityPageName.case; -const isAlertsRoutes = (spyState: RouteSpyState) => - spyState != null && spyState.pageName === SecurityPageName.detections; - const isAdminRoutes = (spyState: RouteSpyState): spyState is AdministrationRouteSpyState => spyState != null && spyState.pageName === SecurityPageName.administration; +const isRulesRoutes = (spyState: RouteSpyState): spyState is AdministrationRouteSpyState => + spyState != null && spyState.pageName === SecurityPageName.rules; + // eslint-disable-next-line complexity export const getBreadcrumbsForRoute = ( objectParam: RouteSpyState & TabNavigationProps, @@ -69,7 +82,7 @@ export const getBreadcrumbsForRoute = ( // Sets `timeline.isOpen` to false in the state to avoid reopening the timeline on breadcrumb click. https://github.com/elastic/kibana/issues/100322 const object = { ...objectParam, timeline: { ...objectParam.timeline, isOpen: false } }; - const overviewPath = getUrlForApp(APP_ID, { path: SecurityPageName.overview }); + const overviewPath = getUrlForApp(APP_ID, { deepLinkId: SecurityPageName.overview }); const siemRootBreadcrumb: ChromeBreadcrumb = { text: APP_NAME, href: getAppOverviewUrl(overviewPath), @@ -80,6 +93,7 @@ export const getBreadcrumbsForRoute = ( if (spyState.tabName != null) { urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; } + return [ siemRootBreadcrumb, ...getHostDetailsBreadcrumbs( @@ -110,8 +124,8 @@ export const getBreadcrumbsForRoute = ( ), ]; } - if (isAlertsRoutes(spyState) && object.navTabs) { - const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false }; + if (isRulesRoutes(spyState) && object.navTabs) { + const tempNav: SearchNavTab = { urlKey: SecurityPageName.rules, isDetailPage: false }; let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; if (spyState.tabName != null) { urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; @@ -129,6 +143,7 @@ export const getBreadcrumbsForRoute = ( ), ]; } + if (isCaseRoutes(spyState) && object.navTabs) { const tempNav: SearchNavTab = { urlKey: 'case', isDetailPage: false }; let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index c75b38e03acb4..5bb0805acc378 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -32,7 +32,8 @@ jest.mock('./breadcrumbs', () => ({ setBreadcrumbs: jest.fn(), })); const mockGetUrlForApp = jest.fn(); -jest.mock('../../lib/kibana', () => { +const mockNavigateToUrl = jest.fn(); +jest.mock('../../lib/kibana/kibana_react', () => { return { useKibana: () => ({ services: { @@ -40,6 +41,7 @@ jest.mock('../../lib/kibana', () => { application: { navigateToApp: jest.fn(), getUrlForApp: mockGetUrlForApp, + navigateToUrl: mockNavigateToUrl, }, }, }), @@ -47,6 +49,13 @@ jest.mock('../../lib/kibana', () => { }); jest.mock('../link_to'); +jest.mock('react-router-dom', () => ({ + useLocation: jest.fn(() => ({ + search: '', + })), + useHistory: jest.fn(), +})); + describe('SIEM Navigation', () => { const mockProps: TabNavigationComponentProps & SecuritySolutionTabNavigationProps & @@ -97,57 +106,7 @@ describe('SIEM Navigation', () => { 1, { detailName: undefined, - navTabs: { - detections: { - disabled: false, - href: '/app/security/detections', - id: 'detections', - name: 'Detections', - urlKey: 'detections', - }, - case: { - disabled: false, - href: '/app/security/cases', - id: 'case', - name: 'Cases', - urlKey: 'case', - }, - administration: { - disabled: false, - href: '/app/security/administration', - id: 'administration', - name: 'Administration', - urlKey: 'administration', - }, - hosts: { - disabled: false, - href: '/app/security/hosts', - id: 'hosts', - name: 'Hosts', - urlKey: 'host', - }, - network: { - disabled: false, - href: '/app/security/network', - id: 'network', - name: 'Network', - urlKey: 'network', - }, - overview: { - disabled: false, - href: '/app/security/overview', - id: 'overview', - name: 'Overview', - urlKey: 'overview', - }, - timelines: { - disabled: false, - href: '/app/security/timelines', - id: 'timelines', - name: 'Timelines', - urlKey: 'timeline', - }, - }, + navTabs, pageName: 'hosts', pathName: '/', search: '', @@ -188,7 +147,8 @@ describe('SIEM Navigation', () => { }, }, undefined, - mockGetUrlForApp + mockGetUrlForApp, + mockNavigateToUrl ); }); test('it calls setBreadcrumbs with correct path on update', () => { @@ -204,57 +164,7 @@ describe('SIEM Navigation', () => { detailName: undefined, filters: [], flowTarget: undefined, - navTabs: { - detections: { - disabled: false, - href: '/app/security/detections', - id: 'detections', - name: 'Detections', - urlKey: 'detections', - }, - case: { - disabled: false, - href: '/app/security/cases', - id: 'case', - name: 'Cases', - urlKey: 'case', - }, - hosts: { - disabled: false, - href: '/app/security/hosts', - id: 'hosts', - name: 'Hosts', - urlKey: 'host', - }, - administration: { - disabled: false, - href: '/app/security/administration', - id: 'administration', - name: 'Administration', - urlKey: 'administration', - }, - network: { - disabled: false, - href: '/app/security/network', - id: 'network', - name: 'Network', - urlKey: 'network', - }, - overview: { - disabled: false, - href: '/app/security/overview', - id: 'overview', - name: 'Overview', - urlKey: 'overview', - }, - timelines: { - disabled: false, - href: '/app/security/timelines', - id: 'timelines', - name: 'Timelines', - urlKey: 'timeline', - }, - }, + navTabs, pageName: 'network', pathName: '/', query: { language: 'kuery', query: '' }, @@ -288,7 +198,8 @@ describe('SIEM Navigation', () => { }, }, undefined, - mockGetUrlForApp + mockGetUrlForApp, + mockNavigateToUrl ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx index 233b4b2cb1d02..71a5309937fff 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx @@ -39,7 +39,7 @@ export const TabNavigationComponent: React.FC< }) => { const { chrome, - application: { getUrlForApp }, + application: { getUrlForApp, navigateToUrl }, } = useKibana().services; useEffect(() => { @@ -62,7 +62,8 @@ export const TabNavigationComponent: React.FC< timerange: urlState.timerange, }, chrome, - getUrlForApp + getUrlForApp, + navigateToUrl ); } }, [ @@ -77,6 +78,7 @@ export const TabNavigationComponent: React.FC< flowTarget, tabName, getUrlForApp, + navigateToUrl, ]); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index 3e66a024c7bd0..18dd07a99824e 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -9,8 +9,6 @@ import { mount } from 'enzyme'; import React from 'react'; import { TimelineTabs } from '../../../../../common/types/timeline'; -import { navTabs } from '../../../../app/home/home_navigations'; -import { SecurityPageName } from '../../../../app/types'; import { navTabsHostDetails } from '../../../../hosts/pages/details/nav_tabs'; import { HostsTableType } from '../../../../hosts/store/model'; import { RouteSpyState } from '../../../utils/route/types'; @@ -18,153 +16,107 @@ import { CONSTANTS } from '../../url_state/constants'; import { TabNavigationComponent } from './'; import { TabNavigationProps } from './types'; -jest.mock('../../../lib/kibana'); jest.mock('../../link_to'); +jest.mock('../../../lib/kibana/kibana_react', () => { + const originalModule = jest.requireActual('../../../../common/lib/kibana/kibana_react'); + return { + ...originalModule, + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + getUrlForApp: (appId: string, options?: { path?: string }) => + `/app/${appId}${options?.path}`, + navigateToApp: jest.fn(), + }, + }, + }), + useUiSetting$: jest.fn().mockReturnValue([]), + }; +}); + +const SEARCH_QUERY = '?search=test'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); return { ...original, - useHistory: () => ({ - push: jest.fn(), - }), + useLocation: jest.fn(() => ({ + search: SEARCH_QUERY, + })), }; }); -describe('Tab Navigation', () => { - const pageName = SecurityPageName.hosts; - const hostName = 'siem-window'; - const tabName = HostsTableType.authentications; - const pathName = `/${pageName}/${hostName}/${tabName}`; +const hostName = 'siem-window'; - describe('Page Navigation', () => { - const mockProps: TabNavigationProps & RouteSpyState = { - pageName, - pathName, - detailName: undefined, - search: '', - tabName, - navTabs, - [CONSTANTS.timerange]: { - global: { - [CONSTANTS.timerange]: { - from: '2019-05-16T23:10:43.696Z', - fromStr: 'now-24h', - kind: 'relative', - to: '2019-05-17T23:10:43.697Z', - toStr: 'now', - }, - linkTo: ['timeline'], - }, - timeline: { - [CONSTANTS.timerange]: { - from: '2019-05-16T23:10:43.696Z', - fromStr: 'now-24h', - kind: 'relative', - to: '2019-05-17T23:10:43.697Z', - toStr: 'now', - }, - linkTo: ['global'], +describe('Table Navigation', () => { + const mockHasMlUserPermissions = true; + const mockProps: TabNavigationProps & RouteSpyState = { + pageName: 'hosts', + pathName: '/hosts', + detailName: undefined, + search: '', + tabName: HostsTableType.authentications, + navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions), + [CONSTANTS.timerange]: { + global: { + [CONSTANTS.timerange]: { + from: '2019-05-16T23:10:43.696Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2019-05-17T23:10:43.697Z', + toStr: 'now', }, + linkTo: ['timeline'], }, - [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, - [CONSTANTS.filters]: [], - [CONSTANTS.sourcerer]: {}, - [CONSTANTS.timeline]: { - activeTab: TimelineTabs.query, - id: '', - isOpen: false, - graphEventId: '', - }, - }; - test('it mounts with correct tab highlighted', () => { - const wrapper = mount(<TabNavigationComponent {...mockProps} />); - const hostsTab = wrapper.find('EuiTab[data-test-subj="navigation-hosts"]'); - expect(hostsTab.prop('isSelected')).toBeTruthy(); - }); - test('it changes active tab when nav changes by props', () => { - const wrapper = mount(<TabNavigationComponent {...mockProps} />); - const networkTab = () => wrapper.find('EuiTab[data-test-subj="navigation-network"]').first(); - expect(networkTab().prop('isSelected')).toBeFalsy(); - wrapper.setProps({ - pageName: 'network', - pathName: '/network', - tabName: undefined, - }); - wrapper.update(); - expect(networkTab().prop('isSelected')).toBeTruthy(); - }); - }); - - describe('Table Navigation', () => { - const mockHasMlUserPermissions = true; - const mockProps: TabNavigationProps & RouteSpyState = { - pageName: 'hosts', - pathName: '/hosts', - detailName: undefined, - search: '', - tabName: HostsTableType.authentications, - navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions), - [CONSTANTS.timerange]: { - global: { - [CONSTANTS.timerange]: { - from: '2019-05-16T23:10:43.696Z', - fromStr: 'now-24h', - kind: 'relative', - to: '2019-05-17T23:10:43.697Z', - toStr: 'now', - }, - linkTo: ['timeline'], + timeline: { + [CONSTANTS.timerange]: { + from: '2019-05-16T23:10:43.696Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2019-05-17T23:10:43.697Z', + toStr: 'now', }, - timeline: { - [CONSTANTS.timerange]: { - from: '2019-05-16T23:10:43.696Z', - fromStr: 'now-24h', - kind: 'relative', - to: '2019-05-17T23:10:43.697Z', - toStr: 'now', - }, - linkTo: ['global'], - }, - }, - [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, - [CONSTANTS.filters]: [], - [CONSTANTS.sourcerer]: {}, - [CONSTANTS.timeline]: { - activeTab: TimelineTabs.query, - id: '', - isOpen: false, - graphEventId: '', + linkTo: ['global'], }, - }; - test('it mounts with correct tab highlighted', () => { - const wrapper = mount(<TabNavigationComponent {...mockProps} />); - const tableNavigationTab = wrapper.find( - `EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]` - ); + }, + [CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' }, + [CONSTANTS.filters]: [], + [CONSTANTS.sourcerer]: {}, + [CONSTANTS.timeline]: { + activeTab: TimelineTabs.query, + id: '', + isOpen: false, + graphEventId: '', + }, + }; + test('it mounts with correct tab highlighted', () => { + const wrapper = mount(<TabNavigationComponent {...mockProps} />); + const tableNavigationTab = wrapper.find( + `EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]` + ); - expect(tableNavigationTab.prop('isSelected')).toBeTruthy(); - }); - test('it changes active tab when nav changes by props', () => { - const wrapper = mount(<TabNavigationComponent {...mockProps} />); - const tableNavigationTab = () => - wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first(); - expect(tableNavigationTab().prop('isSelected')).toBeFalsy(); - wrapper.setProps({ - pageName: SecurityPageName.hosts, - pathName: `/${SecurityPageName.hosts}`, - tabName: HostsTableType.events, - }); - wrapper.update(); - expect(tableNavigationTab().prop('isSelected')).toBeTruthy(); - }); - test('it carries the url state in the link', () => { - const wrapper = mount(<TabNavigationComponent {...mockProps} />); - const firstTab = wrapper.find( - `EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]` - ); - expect(firstTab.props().href).toBe('/siem-window/authentications'); + expect(tableNavigationTab.prop('isSelected')).toBeTruthy(); + }); + test('it changes active tab when nav changes by props', () => { + const wrapper = mount(<TabNavigationComponent {...mockProps} />); + const tableNavigationTab = () => + wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first(); + expect(tableNavigationTab().prop('isSelected')).toBeFalsy(); + wrapper.setProps({ + tabName: HostsTableType.events, }); + wrapper.update(); + expect(tableNavigationTab().prop('isSelected')).toBeTruthy(); + }); + test('it carries the url state in the link', () => { + const wrapper = mount(<TabNavigationComponent {...mockProps} />); + + const firstTab = wrapper.find( + `EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]` + ); + expect(firstTab.props().href).toBe( + `/app/securitySolution/hosts/siem-window/authentications${SEARCH_QUERY}` + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx index 92596945a4769..2ca0d878078aa 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx @@ -8,48 +8,35 @@ import { EuiTab, EuiTabs } from '@elastic/eui'; import { getOr } from 'lodash/fp'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import deepEqual from 'fast-deep-equal'; -import { APP_ID } from '../../../../../common/constants'; +import { useNavigation } from '../../../lib/kibana/hooks'; import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry'; -import { getSearch } from '../helpers'; import { TabNavigationProps, TabNavigationItemProps } from './types'; -import { useKibana } from '../../../lib/kibana'; -import { SecurityPageName } from '../../../../app/types'; -import { useFormatUrl } from '../../link_to'; const TabNavigationItemComponent = ({ disabled, - href, hrefWithSearch, id, name, isSelected, - pageId, - urlSearch, }: TabNavigationItemProps) => { - const history = useHistory(); - const { navigateToApp, getUrlForApp } = useKibana().services.application; - const { formatUrl } = useFormatUrl(((pageId ?? id) as unknown) as SecurityPageName); + const { getAppUrl, navigateTo } = useNavigation(); + const handleClick = useCallback( (ev) => { ev.preventDefault(); - if (id in SecurityPageName && pageId == null) { - navigateToApp(`${APP_ID}:${id}`, { path: urlSearch }); - } else { - history.push(hrefWithSearch); - } + navigateTo({ path: hrefWithSearch }); track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`); }, - [history, hrefWithSearch, id, navigateToApp, pageId, urlSearch] + [navigateTo, hrefWithSearch, id] ); - const appHref = - pageId != null - ? formatUrl(href) - : getUrlForApp(`${APP_ID}:${id}`, { - path: urlSearch, - }); + + const appHref = getAppUrl({ + path: hrefWithSearch, + }); + return ( <EuiTab data-href={appHref} @@ -68,28 +55,17 @@ const TabNavigationItem = React.memo(TabNavigationItemComponent); export const TabNavigationComponent: React.FC<TabNavigationProps> = ({ display, - filters, - query, navTabs, - pageName, - savedQuery, - sourcerer, tabName, - timeline, - timerange, }) => { const mapLocationToTab = useCallback( (): string => getOr( '', 'id', - Object.values(navTabs).find( - (item) => - (tabName === item.id && item.pageId != null) || - (pageName === item.id && item.pageId == null) - ) + Object.values(navTabs).find((item) => tabName === item.id) ), - [pageName, tabName, navTabs] + [tabName, navTabs] ); const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab()); useEffect(() => { @@ -100,38 +76,27 @@ export const TabNavigationComponent: React.FC<TabNavigationProps> = ({ } // we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies) - }, [pageName, tabName, navTabs, mapLocationToTab, selectedTabId]); + }, [tabName, navTabs, mapLocationToTab, selectedTabId]); + + const { search } = useLocation(); const renderTabs = useMemo( () => Object.values(navTabs).map((tab) => { const isSelected = selectedTabId === tab.id; - const search = getSearch(tab, { - filters, - query, - savedQuery, - sourcerer, - timeline, - timerange, - }); - const hrefWithSearch = - tab.href + getSearch(tab, { filters, query, savedQuery, sourcerer, timeline, timerange }); return ( <TabNavigationItem key={`navigation-${tab.id}`} id={tab.id} - href={tab.href} - hrefWithSearch={hrefWithSearch} + hrefWithSearch={tab.href + search} name={tab.name} disabled={tab.disabled} - pageId={tab.pageId} isSelected={isSelected} - urlSearch={search} /> ); }), - [navTabs, selectedTabId, filters, query, savedQuery, sourcerer, timeline, timerange] + [navTabs, selectedTabId, search] ); return <EuiTabs display={display}>{renderTabs}</EuiTabs>; @@ -143,15 +108,8 @@ export const TabNavigation = React.memo( TabNavigationComponent, (prevProps, nextProps) => prevProps.display === nextProps.display && - prevProps.pageName === nextProps.pageName && - prevProps.savedQuery === nextProps.savedQuery && prevProps.tabName === nextProps.tabName && - deepEqual(prevProps.filters, nextProps.filters) && - deepEqual(prevProps.query, nextProps.query) && - deepEqual(prevProps.navTabs, nextProps.navTabs) && - deepEqual(prevProps.sourcerer, nextProps.sourcerer) && - deepEqual(prevProps.timeline, nextProps.timeline) && - deepEqual(prevProps.timerange, nextProps.timerange) + deepEqual(prevProps.navTabs, nextProps.navTabs) ); TabNavigation.displayName = 'TabNavigation'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts index 53565d79e6948..c99d50698db2b 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/types.ts @@ -27,12 +27,9 @@ export interface TabNavigationProps extends SecuritySolutionTabNavigationProps { } export interface TabNavigationItemProps { - href: string; hrefWithSearch: string; id: string; disabled: boolean; name: string; isSelected: boolean; - urlSearch: string; - pageId?: string; } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index 1c317700b1d15..1b1b3c9af4bfc 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -6,7 +6,7 @@ */ import { UrlStateType } from '../url_state/constants'; -import { SecurityPageName } from '../../../app/types'; +import { SecurityPageName, SecurityPageGroupName } from '../../../app/types'; import { UrlState } from '../url_state/types'; import { SiemRouteType } from '../../utils/route/types'; @@ -23,13 +23,25 @@ export interface TabNavigationComponentProps { export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean }; +export interface NavGroupTab { + id: string; + name: string; +} + +export type SecurityNavTabGroupKey = + | SecurityPageGroupName.detect + | SecurityPageGroupName.explore + | SecurityPageGroupName.investigate + | SecurityPageGroupName.manage; + +export type NavTabGroups = Record<SecurityNavTabGroupKey, NavGroupTab>; + export interface NavTab { id: string; name: string; href: string; disabled: boolean; - urlKey: UrlStateType; - isDetailPage?: boolean; + urlKey?: UrlStateType; pageId?: SecurityPageName; } @@ -37,14 +49,21 @@ export type SiemNavTabKey = | SecurityPageName.overview | SecurityPageName.hosts | SecurityPageName.network - | SecurityPageName.detections + | SecurityPageName.alerts + | SecurityPageName.rules + | SecurityPageName.exceptions | SecurityPageName.timelines | SecurityPageName.case - | SecurityPageName.administration; + | SecurityPageName.administration + | SecurityPageName.endpoints + | SecurityPageName.trustedApps + | SecurityPageName.eventFilters; export type SiemNavTab = Record<SiemNavTabKey, NavTab>; export type GetUrlForApp = ( appId: string, - options?: { path?: string; absolute?: boolean } + options?: { deepLinkId?: string; path?: string; absolute?: boolean } ) => string; + +export type NavigateToUrl = (url: string) => void; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index ef00bef841305..7e211a2e95152 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -74,8 +74,8 @@ describe('useSecuritySolutionNavigation', () => { services: { application: { navigateToApp: jest.fn(), - getUrlForApp: (appId: string, options?: { path?: string; absolute?: boolean }) => - `${appId}${options?.path ?? ''}`, + getUrlForApp: (appId: string, options?: { path?: string; deepLinkId?: boolean }) => + `${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`, }, chrome: { setBreadcrumbs: jest.fn(), @@ -97,67 +97,131 @@ describe('useSecuritySolutionNavigation', () => { "id": "securitySolution", "items": Array [ Object { - "data-href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-overview", "disabled": false, - "href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "overview", "isSelected": false, "name": "Overview", "onClick": [Function], }, + ], + "name": "", + }, + Object { + "id": "detect", + "items": Array [ + Object { + "data-href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-alerts", + "disabled": false, + "href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "alerts", + "isSelected": false, + "name": "Alerts", + "onClick": [Function], + }, Object { - "data-href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", - "data-test-subj": "navigation-detections", + "data-href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-rules", "disabled": false, - "href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", - "id": "detections", + "href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "rules", "isSelected": false, - "name": "Detections", + "name": "Rules", "onClick": [Function], }, Object { - "data-href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-exceptions", + "disabled": false, + "href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "exceptions", + "isSelected": false, + "name": "Exceptions", + "onClick": [Function], + }, + ], + "name": "Detect", + }, + Object { + "id": "explore", + "items": Array [ + Object { + "data-href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-hosts", "disabled": false, - "href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "hosts", "isSelected": true, "name": "Hosts", "onClick": [Function], }, Object { - "data-href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-network", "disabled": false, - "href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "network", "isSelected": false, "name": "Network", "onClick": [Function], }, + ], + "name": "Explore", + }, + Object { + "id": "investigate", + "items": Array [ Object { - "data-href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-timelines", "disabled": false, - "href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "timelines", "isSelected": false, "name": "Timelines", "onClick": [Function], }, + ], + "name": "Investigate", + }, + Object { + "id": "manage", + "items": Array [ Object { - "data-href": "securitySolution:administration", - "data-test-subj": "navigation-administration", + "data-href": "securitySolution/endpoints", + "data-test-subj": "navigation-endpoints", "disabled": false, - "href": "securitySolution:administration", - "id": "administration", + "href": "securitySolution/endpoints", + "id": "endpoints", "isSelected": false, - "name": "Administration", + "name": "Endpoints", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution/trusted_apps", + "data-test-subj": "navigation-trusted_apps", + "disabled": false, + "href": "securitySolution/trusted_apps", + "id": "trusted_apps", + "isSelected": false, + "name": "Trusted Applications", + "onClick": [Function], + }, + Object { + "data-href": "securitySolution/event_filters", + "data-test-subj": "navigation-event_filters", + "disabled": false, + "href": "securitySolution/event_filters", + "id": "event_filters", + "isSelected": false, + "name": "Event Filters", "onClick": [Function], }, ], - "name": "", + "name": "Manage", }, ], "name": "Security", @@ -177,15 +241,15 @@ describe('useSecuritySolutionNavigation', () => { useSecuritySolutionNavigation() ); - const caseNavItem = (result.current?.items || [])[0].items?.find( + const caseNavItem = (result.current?.items || [])[3].items?.find( (item) => item['data-test-subj'] === 'navigation-case' ); expect(caseNavItem).toMatchInlineSnapshot(` Object { - "data-href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-case", "disabled": false, - "href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "case", "isSelected": false, "name": "Cases", @@ -204,7 +268,7 @@ describe('useSecuritySolutionNavigation', () => { useSecuritySolutionNavigation() ); - const caseNavItem = (result.current?.items || [])[0].items?.find( + const caseNavItem = (result.current?.items || [])[3].items?.find( (item) => item['data-test-subj'] === 'navigation-case' ); expect(caseNavItem).toBeFalsy(); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx index f2aee86912dd7..39c6885e8dff5 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx @@ -6,15 +6,13 @@ */ import { useEffect } from 'react'; -import { pickBy } from 'lodash/fp'; import { usePrimaryNavigation } from './use_primary_navigation'; -import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { useKibana } from '../../../lib/kibana'; import { setBreadcrumbs } from '../breadcrumbs'; import { makeMapStateToProps } from '../../url_state/helpers'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; import { navTabs } from '../../../../app/home/home_navigations'; import { useDeepEqualSelector } from '../../../hooks/use_selector'; -import { SecurityPageName } from '../../../../../common/constants'; /** * @description - This hook provides the structure necessary by the KibanaPageTemplate for rendering the primary security_solution side navigation. @@ -26,7 +24,7 @@ export const useSecuritySolutionNavigation = () => { const { urlState } = useDeepEqualSelector(urlMapState); const { chrome, - application: { getUrlForApp }, + application: { getUrlForApp, navigateToUrl }, } = useKibana().services; const { detailName, flowTarget, pageName, pathName, search, state, tabName } = routeProps; @@ -51,7 +49,8 @@ export const useSecuritySolutionNavigation = () => { timerange: urlState.timerange, }, chrome, - getUrlForApp + getUrlForApp, + navigateToUrl ); } }, [ @@ -65,25 +64,17 @@ export const useSecuritySolutionNavigation = () => { flowTarget, tabName, getUrlForApp, + navigateToUrl, ]); - const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; - - // build a list of tabs to exclude - const tabsToExclude = new Set<string>([ - ...(!hasCasesReadPermissions ? [SecurityPageName.case] : []), - ]); - - // include the tab if it is not in the set of excluded ones - const tabsToDisplay = pickBy((_, key) => !tabsToExclude.has(key), navTabs); - return usePrimaryNavigation({ query: urlState.query, filters: urlState.filters, - navTabs: tabsToDisplay, + navTabs, pageName, sourcerer: urlState.sourcerer, savedQuery: urlState.savedQuery, + tabName, timeline: urlState.timeline, timerange: urlState.timerange, }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts index f639b8a37f0da..f2c68f881528d 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/types.ts @@ -12,4 +12,4 @@ export type PrimaryNavigationItemsProps = Omit< 'pathName' | 'pageName' | 'tabName' > & { selectedTabId: string }; -export type PrimaryNavigationProps = Omit<TabNavigationProps, 'pathName' | 'tabName'>; +export type PrimaryNavigationProps = Omit<TabNavigationProps, 'pathName'>; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index 42ca7f4c65460..e04ec7727a08f 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -5,62 +5,85 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types'; +import { navTabGroups } from '../../../../app/home/home_navigations'; import { APP_ID } from '../../../../../common/constants'; -import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry'; import { getSearch } from '../helpers'; import { PrimaryNavigationItemsProps } from './types'; -import { useKibana } from '../../../lib/kibana'; +import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana'; +import { NavTab } from '../types'; export const usePrimaryNavigationItems = ({ - filters, navTabs, - query, - savedQuery, selectedTabId, - sourcerer, - timeline, - timerange, -}: PrimaryNavigationItemsProps) => { + ...urlStateProps +}: PrimaryNavigationItemsProps): Array<EuiSideNavItemType<{}>> => { const { navigateToApp, getUrlForApp } = useKibana().services.application; - const navItems = Object.values(navTabs).map((tab) => { - const { id, name, disabled } = tab; - const isSelected = selectedTabId === id; - const urlSearch = getSearch(tab, { - filters, - query, - savedQuery, - sourcerer, - timeline, - timerange, - }); + const getSideNav = useCallback( + (tab: NavTab) => { + const { id, name, disabled } = tab; + const isSelected = selectedTabId === id; + const urlSearch = getSearch(tab, urlStateProps); - const handleClick = (ev: React.MouseEvent) => { - ev.preventDefault(); - navigateToApp(`${APP_ID}:${id}`, { path: urlSearch }); - track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`); - }; + const handleClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + navigateToApp(APP_ID, { deepLinkId: id, path: urlSearch }); + }; - const appHref = getUrlForApp(`${APP_ID}:${id}`, { path: urlSearch }); + const appHref = getUrlForApp(APP_ID, { deepLinkId: id, path: urlSearch }); - return { - 'data-href': appHref, - 'data-test-subj': `navigation-${id}`, - disabled, - href: appHref, - id, - isSelected, - name, - onClick: handleClick, - }; - }); + return { + 'data-href': appHref, + 'data-test-subj': `navigation-${id}`, + disabled, + href: appHref, + id, + isSelected, + name, + onClick: handleClick, + }; + }, + [getUrlForApp, navigateToApp, selectedTabId, urlStateProps] + ); + + const navItemsToDisplay = usePrimaryNavigationItemsToDisplay(navTabs); + + return useMemo( + () => + navItemsToDisplay.map((item) => ({ + ...item, + items: item.items.map((t: NavTab) => getSideNav(t)), + })), + [getSideNav, navItemsToDisplay] + ); +}; + +function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { + const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; return [ { - id: APP_ID, // TODO: When separating into sub-sections (detect, explore, investigate). Those names can also serve as the section id - items: navItems, + id: APP_ID, name: '', + items: [navTabs.overview], + }, + { + ...navTabGroups.detect, + items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], + }, + { + ...navTabGroups.explore, + items: [navTabs.hosts, navTabs.network], + }, + { + ...navTabGroups.investigate, + items: hasCasesReadPermissions ? [navTabs.timelines, navTabs.case] : [navTabs.timelines], + }, + { + ...navTabGroups.manage, + items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters], }, ]; -}; +} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx index 390f44b48b0b1..c1abcc1177c80 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { getOr } from 'lodash/fp'; import { useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; @@ -24,17 +23,13 @@ export const usePrimaryNavigation = ({ pageName, savedQuery, sourcerer, + tabName, timeline, timerange, }: PrimaryNavigationProps): KibanaPageTemplateProps['solutionNav'] => { const mapLocationToTab = useCallback( - (): string => - getOr( - '', - 'id', - Object.values(navTabs).find((item) => pageName === item.id && item.pageId == null) - ), - [pageName, navTabs] + (): string => ((tabName && navTabs[tabName]) || navTabs[pageName])?.id ?? '', + [pageName, tabName, navTabs] ); const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab()); @@ -50,11 +45,11 @@ export const usePrimaryNavigation = ({ }, [pageName, navTabs, mapLocationToTab, selectedTabId]); const navItems = usePrimaryNavigationItems({ - filters, navTabs, + selectedTabId, + filters, query, savedQuery, - selectedTabId, sourcerer, timeline, timerange, diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts index 6149300f68e36..6107b61638888 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts @@ -7,9 +7,9 @@ export enum CONSTANTS { appQuery = 'query', + alertsPage = 'alerts.page', caseDetails = 'case.details', casePage = 'case.page', - detectionsPage = 'detections.page', filters = 'filters', hostsDetails = 'hosts.details', hostsPage = 'hosts.page', @@ -27,7 +27,9 @@ export enum CONSTANTS { export type UrlStateType = | 'case' - | 'detections' + | 'alerts' + | 'rules' + | 'exceptions' | 'host' | 'network' | 'overview' diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index a5fbaf9ebb76b..8908b83fc9b56 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -92,8 +92,12 @@ export const getUrlType = (pageName: string): UrlStateType => { return 'host'; } else if (pageName === SecurityPageName.network) { return 'network'; - } else if (pageName === SecurityPageName.detections) { - return 'detections'; + } else if (pageName === SecurityPageName.alerts) { + return 'alerts'; + } else if (pageName === SecurityPageName.rules) { + return 'rules'; + } else if (pageName === SecurityPageName.exceptions) { + return 'exceptions'; } else if (pageName === SecurityPageName.timelines) { return 'timeline'; } else if (pageName === SecurityPageName.case) { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx index 2157b21179f15..b40799895e8a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx @@ -170,7 +170,7 @@ describe('UrlStateContainer', () => { }); }); - describe('After Initialization, keep Relative Date up to date for global only on detections page', () => { + describe('After Initialization, keep Relative Date up to date for global only on alerts page', () => { test.each(testCases)( '%o', async (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { @@ -196,7 +196,7 @@ describe('UrlStateContainer', () => { }); wrapper.update(); - if (CONSTANTS.detectionsPage === page) { + if (CONSTANTS.alertsPage === page) { await waitFor(() => { expect(mockSetRelativeRangeDatePicker.mock.calls[3][0]).toEqual({ from: '2020-01-01T00:00:00.000Z', diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index 8a7c6bcb4a9b5..9fc2e24221bcb 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -45,7 +45,7 @@ export const dispatchSetInitialStateFromUrl = ( const sourcererState = decodeRisonUrlState<SourcererScopePatterns>(newUrlStateString); if (sourcererState != null) { const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( - (key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.detections) + (key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.alerts) ) as SourcererScopeName[]; activeScopes.forEach((scope) => dispatch( diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts index 1a8d512d211e6..63511c54d28db 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts @@ -34,7 +34,23 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ ]; export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = { - detections: [ + alerts: [ + CONSTANTS.appQuery, + CONSTANTS.filters, + CONSTANTS.savedQuery, + CONSTANTS.sourcerer, + CONSTANTS.timerange, + CONSTANTS.timeline, + ], + rules: [ + CONSTANTS.appQuery, + CONSTANTS.filters, + CONSTANTS.savedQuery, + CONSTANTS.sourcerer, + CONSTANTS.timerange, + CONSTANTS.timeline, + ], + exceptions: [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, @@ -88,7 +104,7 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = { export type LocationTypes = | CONSTANTS.caseDetails | CONSTANTS.casePage - | CONSTANTS.detectionsPage + | CONSTANTS.alertsPage | CONSTANTS.hostsDetails | CONSTANTS.hostsPage | CONSTANTS.networkDetails diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx index 7785fa6af2569..10d586c2d7441 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx @@ -221,7 +221,7 @@ export const useUrlStateHooks = ({ } }); } else if (pathName !== prevProps.pathName) { - handleInitialize(type, pageName === SecurityPageName.detections); + handleInitialize(type, pageName === SecurityPageName.alerts); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isInitializing, history, pathName, pageName, prevProps, urlState]); diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 3bc92dafd351f..f619e6565b06b 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -158,8 +158,10 @@ export const useFetchIndex = ( next: (response) => { if (isCompleteResponse(response)) { const stringifyIndices = response.indicesExist.sort().join(); + previousIndexesName.current = response.indicesExist; setLoading(false); + setState({ browserFields: getBrowserFields(stringifyIndices, response.indexFields), docValueFields: getDocValueFields(stringifyIndices, response.indexFields), @@ -167,6 +169,7 @@ export const useFetchIndex = ( indexExists: response.indicesExist.length > 0, indexPatterns: getIndexFields(stringifyIndices, response.indexFields), }); + searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); @@ -187,7 +190,7 @@ export const useFetchIndex = ( abortCtrl.current.abort(); asyncSearch(); }, - [data.search, addError, addWarning, onlyCheckIfIndicesExist] + [data.search, addError, addWarning, onlyCheckIfIndicesExist, setLoading, setState] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts index 862bb43a08d38..148deda9aec76 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts @@ -37,7 +37,7 @@ export const useNavigateToAppEventHandler = <S = unknown>( options?: NavigateToAppHandlerOptions<S> ): EventHandlerCallback => { const { services } = useKibana(); - const { path, state, onClick } = options || {}; + const { path, state, onClick, deepLinkId } = options || {}; return useCallback( (ev) => { try { @@ -70,8 +70,8 @@ export const useNavigateToAppEventHandler = <S = unknown>( } ev.preventDefault(); - services.application.navigateToApp(appId, { path, state }); + services.application.navigateToApp(appId, { deepLinkId, path, state }); }, - [appId, onClick, path, services.application, state] + [appId, deepLinkId, onClick, path, services.application, state] ); }; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts index 09c3d2537e272..61ce5a8238b52 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts @@ -17,6 +17,8 @@ import { createStartServicesMock, createWithKibanaMock, } from '../kibana_react.mock'; +import { APP_ID } from '../../../../../common/constants'; + const mockStartServicesMock = createStartServicesMock(); export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; export const useKibana = jest.fn().mockReturnValue({ @@ -60,3 +62,10 @@ export const useCurrentUser = jest.fn(); export const withKibana = jest.fn(createWithKibanaMock()); export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock()); export const useGetUserCasesPermissions = jest.fn(); +export const useAppUrl = jest.fn().mockReturnValue({ + getAppUrl: jest + .fn() + .mockImplementation(({ appId = APP_ID, ...options }) => + mockStartServicesMock.application.getUrlForApp(appId, options) + ), +}); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index 4a2caefba1b97..1b05c6a857263 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { camelCase, isArray, isObject } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; -import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; +import { APP_ID, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; import { errorToToaster, useStateToaster } from '../../components/toasters'; import { AuthenticatedUser } from '../../../../../security/common/model'; import { StartServices } from '../../../types'; @@ -160,3 +160,57 @@ export const useGetUserCasesPermissions = () => { return casesPermissions; }; + +/** + * Returns a full URL to the provided page path by using + * kibana's `getUrlForApp()` + */ +export const useAppUrl = () => { + const { getUrlForApp } = useKibana().services.application; + + const getAppUrl = useCallback( + ({ appId = APP_ID, ...options }: { appId?: string; deepLinkId?: string; path?: string }) => + getUrlForApp(appId, options), + [getUrlForApp] + ); + return { getAppUrl }; +}; + +/** + * Navigate to any app using kibana's `navigateToApp()` + * or by url using `navigateToUrl()` + */ +export const useNavigateTo = () => { + const { navigateToApp, navigateToUrl } = useKibana().services.application; + + const navigateTo = useCallback( + ({ + url, + appId = APP_ID, + ...options + }: { + url?: string; + appId?: string; + deepLinkId?: string; + path?: string; + }) => { + if (url) { + navigateToUrl(url); + } else { + navigateToApp(appId, options); + } + }, + [navigateToApp, navigateToUrl] + ); + return { navigateTo }; +}; + +/** + * Returns navigateTo and getAppUrl navigation hooks + * + */ +export const useNavigation = () => { + const { navigateTo } = useNavigateTo(); + const { getAppUrl } = useAppUrl(); + return { navigateTo, getAppUrl }; +}; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index 3e582ee9b20c8..44a100e27e95b 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; -import { createMemoryHistory } from 'history'; +import { createMemoryHistory, MemoryHistory } from 'history'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; import { Action, Reducer, Store } from 'redux'; +import { AppDeepLink } from 'kibana/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { StartPlugins, StartServices } from '../../../types'; import { depsStartMock } from './dependencies_start_mock'; @@ -21,10 +22,10 @@ import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock'; import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { PLUGIN_ID } from '../../../../../fleet/common'; -import { APP_ID } from '../../../../common/constants'; +import { APP_ID, APP_PATH } from '../../../../common/constants'; import { KibanaContextProvider, KibanaServices } from '../../lib/kibana'; -import { MANAGEMENT_APP_ID } from '../../../management/common/constants'; import { fleetGetPackageListHttpMock } from '../../../management/pages/endpoint_hosts/mocks'; +import { getDeepLinks } from '../../../app/deep_links'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -93,7 +94,7 @@ const experimentalFeaturesReducer: Reducer<State['app'], UpdateExperimentalFeatu */ export const createAppRootMockRenderer = (): AppContextTestRender => { const history = createMemoryHistory<never>(); - const coreStart = createCoreStartMock(); + const coreStart = createCoreStartMock(history); const depsStart = depsStartMock(); const middlewareSpy = createSpyMiddleware(); const { storage } = createSecuritySolutionStorageMock(); @@ -166,26 +167,56 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { }; }; -const createCoreStartMock = (): ReturnType<typeof coreMock.createStart> => { +const createCoreStartMock = ( + history: MemoryHistory<never> +): ReturnType<typeof coreMock.createStart> => { const coreStart = coreMock.createStart({ basePath: '/mock' }); + const deepLinkPaths = getDeepLinkPaths(getDeepLinks()); + // Mock the certain APP Ids returned by `application.getUrlForApp()` - coreStart.application.getUrlForApp.mockImplementation((appId) => { + coreStart.application.getUrlForApp.mockImplementation((appId, { deepLinkId, path } = {}) => { switch (appId) { case PLUGIN_ID: return '/app/fleet'; case APP_ID: - return '/app/security'; - case MANAGEMENT_APP_ID: - return '/app/security/administration'; + return `${APP_PATH}${ + deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : '' + }${path ?? ''}`; default: return `${appId} not mocked!`; } }); + coreStart.application.navigateToApp.mockImplementation((appId, { deepLinkId, path } = {}) => { + if (appId === APP_ID) { + history.push( + `${deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''}${path ?? ''}` + ); + } + return Promise.resolve(); + }); + + coreStart.application.navigateToUrl.mockImplementation((url) => { + history.push(url.replace(APP_PATH, '')); + return Promise.resolve(); + }); + return coreStart; }; +const getDeepLinkPaths = (deepLinks: AppDeepLink[]): Record<string, string> => { + return deepLinks.reduce((result: Record<string, string>, deepLink) => { + if (deepLink.path) { + result[deepLink.id] = deepLink.path; + } + if (deepLink.deepLinks) { + return { ...result, ...getDeepLinkPaths(deepLink.deepLinks) }; + } + return result; + }, {}); +}; + const applyDefaultCoreHttpMocks = (http: AppContextTestRender['coreStart']['http']) => { // Need to mock getting the endpoint package from the fleet API because it is used as soon // as the store middleware for Endpoint list is initialized, thus mocking it here would avoid diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx index 19d1e9c219191..31d1ce6d41153 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.test.tsx @@ -12,6 +12,8 @@ import { shallow, mount } from 'enzyme'; import '../../../common/mock/match_media'; import { esQuery } from '../../../../../../../src/plugins/data/public'; import { TestProviders } from '../../../common/mock'; +import { SecurityPageName } from '../../../app/types'; + import { AlertsHistogramPanel, buildCombinedQueries, parseCombinedQueries } from './index'; import * as helpers from './helpers'; @@ -83,7 +85,10 @@ describe('AlertsHistogramPanel', () => { preventDefault: jest.fn(), }); - expect(mockNavigateToApp).toBeCalledWith('securitySolution:detections', { path: '' }); + expect(mockNavigateToApp).toBeCalledWith('securitySolution', { + deepLinkId: SecurityPageName.alerts, + path: '', + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx index d766104e356eb..8a328c14726c9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx @@ -152,7 +152,7 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( }); const kibana = useKibana(); const { navigateToApp } = kibana.services.application; - const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.detections); + const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.alerts); const totalAlerts = useMemo( () => @@ -175,7 +175,8 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( const goToDetectionEngine = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index cbdfe5b246aff..880817af856f8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -27,6 +27,7 @@ jest.mock('react-router-dom', () => { }); jest.mock('../../../../common/components/link_to'); +jest.mock('../../../../common/lib/kibana'); jest.mock('../../../containers/detection_engine/rules/api', () => ({ getPrePackagedRulesStatus: jest.fn().mockResolvedValue({ @@ -52,11 +53,14 @@ describe('PrePackagedRulesPrompt', () => { let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>; beforeEach(() => { - jest.resetAllMocks(); appToastsMock = useAppToastsMock.create(); (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('renders correctly', () => { const wrapper = shallow(<PrePackagedRulesPrompt {...props} />); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 56875bcc4f88c..5688b4065ab76 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -9,7 +9,6 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { memo, useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import { useHistory } from 'react-router-dom'; import { getCreateRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import * as i18n from './translations'; import { LinkButton } from '../../../../common/components/links'; @@ -17,6 +16,8 @@ import { SecurityPageName } from '../../../../app/types'; import { useFormatUrl } from '../../../../common/components/link_to'; import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { useUserData } from '../../user_info'; +import { APP_ID } from '../../../../../common/constants'; +import { useKibana } from '../../../../common/lib/kibana'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ @@ -35,18 +36,18 @@ const PrePackagedRulesPromptComponent: React.FC<PrePackagedRulesPromptProps> = ( loading = false, userHasPermissions = false, }) => { - const history = useHistory(); const handlePreBuiltCreation = useCallback(() => { createPrePackagedRules(); }, [createPrePackagedRules]); - const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { formatUrl } = useFormatUrl(SecurityPageName.rules); + const { navigateToApp } = useKibana().services.application; const goToCreateRule = useCallback( (ev) => { ev.preventDefault(); - history.push(getCreateRuleUrl()); + navigateToApp(APP_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); }, - [history] + [navigateToApp] ); const [ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx index 3a27469ba2539..c545de7fd8d7d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx @@ -16,6 +16,20 @@ import { import { RuleActionsOverflow } from './index'; import { mockRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +jest.mock('../../../../common/lib/kibana', () => { + const actual = jest.requireActual('../../../../common/lib/kibana'); + return { + ...actual, + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + navigateToApp: jest.fn(), + }, + }, + }), + }; +}); + jest.mock('react-router-dom', () => ({ useHistory: () => ({ push: jest.fn(), @@ -28,21 +42,14 @@ jest.mock('../../../pages/detection_engine/rules/all/actions', () => ({ editRuleAction: jest.fn(), })); -jest.mock('../../../../common/lib/kibana', () => { - return { - KibanaServices: { - get: () => ({ - http: { fetch: jest.fn() }, - }), - }, - }; -}); - const duplicateRulesActionMock = duplicateRulesAction as jest.Mock; const flushPromises = () => new Promise(setImmediate); describe('RuleActionsOverflow', () => { afterEach(() => { + jest.clearAllMocks(); + }); + afterAll(() => { jest.resetAllMocks(); }); @@ -229,7 +236,7 @@ describe('RuleActionsOverflow', () => { await flushPromises(); expect(duplicateRulesAction).toHaveBeenCalled(); - expect(editRuleAction).toHaveBeenCalledWith(ruleDuplicate, expect.anything()); + expect(editRuleAction).toHaveBeenCalledWith(ruleDuplicate.id, expect.anything()); }); describe('rules details export rule', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index e0841824d512f..2146123deafd5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -30,6 +30,7 @@ import { import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { getToolTipContent } from '../../../../common/utils/privileges'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { useKibana } from '../../../../common/lib/kibana'; const MyEuiButtonIcon = styled(EuiButtonIcon)` &.euiButtonIcon { @@ -58,6 +59,7 @@ const RuleActionsOverflowComponent = ({ }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); const history = useHistory(); + const { navigateToApp } = useKibana().services.application; const [, dispatchToaster] = useStateToaster(); const onRuleDeletedCallback = useCallback(() => { @@ -82,7 +84,7 @@ const RuleActionsOverflowComponent = ({ dispatchToaster ); if (createdRules?.length) { - editRuleAction(createdRules[0], history); + editRuleAction(createdRules[0].id, navigateToApp); } }} > @@ -123,7 +125,7 @@ const RuleActionsOverflowComponent = ({ canDuplicateRuleWithActions, closePopover, dispatchToaster, - history, + navigateToApp, onRuleDeletedCallback, rule, userHasPermissions, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 162f86c543308..6e21987db1668 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -302,7 +302,6 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ ), [threatBrowserFields, threatIndexPatternsLoading, threatIndexPatterns, indexPatterns] ); - return isReadOnlyView ? ( <StepContentWrapper data-test-subj="definitionRule" addPadding={addPadding}> <StepRuleDescription @@ -343,7 +342,6 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ 'data-test-subj': 'detectionEngineStepDefineRuleIndices', euiFieldProps: { fullWidth: true, - isDisabled: isLoading, placeholder: '', }, }} diff --git a/x-pack/plugins/security_solution/public/detections/index.ts b/x-pack/plugins/security_solution/public/detections/index.ts index b6b26fde73edb..ca35efe4d9294 100644 --- a/x-pack/plugins/security_solution/public/detections/index.ts +++ b/x-pack/plugins/security_solution/public/detections/index.ts @@ -8,10 +8,10 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline'; -import { AlertsRoutes } from './routes'; +import { routes } from './routes'; import { SecuritySubPlugin } from '../app/types'; -const DETECTIONS_TIMELINE_IDS: TimelineIdLiteral[] = [ +export const DETECTIONS_TIMELINE_IDS: TimelineIdLiteral[] = [ TimelineId.detectionsRulesDetailsPage, TimelineId.detectionsPage, ]; @@ -21,10 +21,10 @@ export class Detections { public start(storage: Storage): SecuritySubPlugin { return { - SubPluginRoutes: AlertsRoutes, storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), }, + routes, }; } } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 0c12d8256d66d..f52b09e2d62b4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -10,10 +10,8 @@ import styled from 'styled-components'; import { noop } from 'lodash/fp'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; -import { isTab } from '../../../../../timelines/public'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; - +import { isTab } from '../../../../../timelines/public'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { SecurityPageName } from '../../../app/types'; import { TimelineId } from '../../../../common/types/timeline'; @@ -25,7 +23,6 @@ import { SiemSearchBar } from '../../../common/components/search_bar'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; import { inputsSelectors } from '../../../common/store/inputs'; import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions'; -import { SpyRoute } from '../../../common/utils/route/spy_routes'; import { useAlertInfo } from '../../components/alerts_info'; import { AlertsTable } from '../../components/alerts_table'; import { NoApiIntegrationKeyCallOut } from '../../components/callouts/no_api_integration_callout'; @@ -59,6 +56,7 @@ import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout'; import { MissingPrivilegesCallOut } from '../../components/callouts/missing_privileges_callout'; +import { useKibana } from '../../../common/lib/kibana'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -103,12 +101,12 @@ const DetectionEnginePageComponent = () => { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, } = useListsConfig(); - const history = useHistory(); const [lastAlerts] = useAlertInfo({}); - const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { formatUrl } = useFormatUrl(SecurityPageName.rules); const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const loading = userInfoLoading || listsConfigLoading; + const { navigateToUrl } = useKibana().services.application; const updateDateRangeCallback = useCallback<UpdateDateRange>( ({ x }) => { @@ -130,9 +128,9 @@ const DetectionEnginePageComponent = () => { const goToRules = useCallback( (ev) => { ev.preventDefault(); - history.push(getRulesUrl()); + navigateToUrl(formatUrl(getRulesUrl())); }, - [history] + [formatUrl, navigateToUrl] ); const alertsHistogramDefaultFilters = useMemo( @@ -288,7 +286,6 @@ const DetectionEnginePageComponent = () => { <OverviewEmpty /> </SecuritySolutionPageWrapper> )} - <SpyRoute pageName={SecurityPageName.detections} /> </> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx deleted file mode 100644 index 57e23452806b6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; - -import '../../../common/mock/match_media'; -import { DetectionEngineContainer } from './index'; - -describe('DetectionEngineContainer', () => { - it('renders correctly', () => { - const wrapper = shallow(<DetectionEngineContainer />); - - expect(wrapper.find('Switch')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx deleted file mode 100644 index 2a3d418f9c3d3..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/index.tsx +++ /dev/null @@ -1,37 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - -import { CreateRulePage } from './rules/create'; -import { DetectionEnginePage } from './detection_engine'; -import { EditRulePage } from './rules/edit'; -import { RuleDetailsPage } from './rules/details'; -import { RulesPage } from './rules'; - -const DetectionEngineContainerComponent: React.FC = () => ( - <Switch> - <Route path="/rules/id/:detailName/edit"> - <EditRulePage /> - </Route> - <Route path="/rules/id/:detailName"> - <RuleDetailsPage /> - </Route> - <Route path="/rules/create"> - <CreateRulePage /> - </Route> - <Route path="/rules"> - <RulesPage /> - </Route> - <Route exact path="" strict> - <DetectionEnginePage /> - </Route> - </Switch> -); - -export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx index 78fac10815d45..214a7ac24da8a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx @@ -5,10 +5,12 @@ * 2.0. */ -import * as H from 'history'; import React, { Dispatch } from 'react'; +import { NavigateToAppOptions } from '../../../../../../../../../src/core/public'; +import { APP_ID } from '../../../../../../common/constants'; import { BulkAction } from '../../../../../../common/detection_engine/schemas/common/schemas'; import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; +import { SecurityPageName } from '../../../../../app/types'; import { getEditRuleUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; import { ActionToaster, @@ -31,8 +33,14 @@ import { transformOutput } from '../../../../containers/detection_engine/rules/t import * as i18n from '../translations'; import { bucketRulesResponse, getExportedRulesCount } from './helpers'; -export const editRuleAction = (rule: Rule, history: H.History) => { - history.push(getEditRuleUrl(rule.id)); +export const editRuleAction = ( + ruleId: string, + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void> +) => { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getEditRuleUrl(ruleId ?? ''), + }); }; export const duplicateRulesAction = async ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.test.tsx index 8eb80bd0d5135..3920aa40e1f15 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.test.tsx @@ -39,26 +39,31 @@ describe('AllRulesTable Columns', () => { test('duplicate rule onClick should call rule edit after the rule is duplicated', async () => { const ruleDuplicate = mockRule('newRule'); + const navigateToApp = jest.fn(); duplicateRulesActionMock.mockImplementation(() => Promise.resolve([ruleDuplicate])); const duplicateRulesActionObject = getActions( dispatch, dispatchToaster, history, + navigateToApp, reFetchRules, refetchPrePackagedRulesStatus, true )[1]; await duplicateRulesActionObject.onClick(rule); expect(duplicateRulesActionMock).toHaveBeenCalled(); - expect(editRuleActionMock).toHaveBeenCalledWith(ruleDuplicate, history); + expect(editRuleActionMock).toHaveBeenCalledWith(ruleDuplicate.id, navigateToApp); }); test('delete rule onClick should call refetch after the rule is deleted', async () => { + const navigateToApp = jest.fn(); + const deleteRulesActionObject = getActions( dispatch, dispatchToaster, history, + navigateToApp, reFetchRules, refetchPrePackagedRulesStatus, true diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index 28a65c3e64e1f..8d0492267258f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -40,11 +40,14 @@ import { LinkAnchor } from '../../../../../common/components/links'; import { getToolTipContent, canEditRuleWithActions } from '../../../../../common/utils/privileges'; import { TagsDisplay } from './tag_display'; import { getRuleStatusText } from '../../../../../../common/detection_engine/utils'; +import { APP_ID, SecurityPageName } from '../../../../../../common/constants'; +import { NavigateToAppOptions } from '../../../../../../../../../src/core/public'; export const getActions = ( dispatch: React.Dispatch<RulesTableAction>, dispatchToaster: Dispatch<ActionToaster>, history: H.History, + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>, reFetchRules: () => Promise<void>, refetchPrePackagedRulesStatus: () => Promise<void>, actionsPrivileges: @@ -64,7 +67,7 @@ export const getActions = ( i18n.EDIT_RULE_SETTINGS ), icon: 'controlsHorizontal', - onClick: (rowItem: Rule) => editRuleAction(rowItem, history), + onClick: (rowItem: Rule) => editRuleAction(rowItem.id, navigateToApp), enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges), }, { @@ -87,7 +90,7 @@ export const getActions = ( dispatchToaster ); if (createdRules?.length) { - editRuleAction(createdRules[0], history); + editRuleAction(createdRules[0].id, navigateToApp); } }, }, @@ -127,6 +130,7 @@ interface GetColumns { hasMlPermissions: boolean; hasPermissions: boolean; loadingRuleIds: string[]; + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>; reFetchRules: () => Promise<void>; refetchPrePackagedRulesStatus: () => Promise<void>; hasReadActionsPrivileges: @@ -144,6 +148,7 @@ export const getColumns = ({ hasMlPermissions, hasPermissions, loadingRuleIds, + navigateToApp, reFetchRules, refetchPrePackagedRulesStatus, hasReadActionsPrivileges, @@ -157,7 +162,10 @@ export const getColumns = ({ data-test-subj="ruleName" onClick={(ev: { preventDefault: () => void }) => { ev.preventDefault(); - history.push(getRuleDetailsUrl(item.id)); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(item.id), + }); }} href={formatUrl(getRuleDetailsUrl(item.id))} > @@ -292,6 +300,7 @@ export const getColumns = ({ dispatch, dispatchToaster, history, + navigateToApp, reFetchRules, refetchPrePackagedRulesStatus, hasReadActionsPrivileges diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx index f64586db9b06c..c4c938d5bb05e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { EuiButtonIcon, EuiBasicTableColumn, EuiToolTip } from '@elastic/eui'; -import { History } from 'history'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import { Spacer } from '../../../../../../common/components/page'; @@ -24,8 +23,8 @@ export type AllExceptionListsColumns = EuiBasicTableColumn<ExceptionListInfo>; export const getAllExceptionListsColumns = ( onExport: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void, onDelete: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void, - history: History, - formatUrl: FormatUrl + formatUrl: FormatUrl, + navigateToUrl: (url: string) => Promise<void> ): AllExceptionListsColumns[] => [ { align: 'left', @@ -81,7 +80,7 @@ export const getAllExceptionListsColumns = ( data-test-subj="ruleName" onClick={(ev: { preventDefault: () => void }) => { ev.preventDefault(); - history.push(getRuleDetailsUrl(id)); + navigateToUrl(formatUrl(getRuleDetailsUrl(id))); }} href={formatUrl(getRuleDetailsUrl(id))} > diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx index 9c2b427948fd8..d86e7b1b7259c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx @@ -42,6 +42,7 @@ export const ExceptionsSearchBar = React.memo<ExceptionListsTableSearchProps>(({ aria-label={i18n.EXCEPTIONS_LISTS_SEARCH_PLACEHOLDER} onChange={onSearch} box={{ + [`data-test-subj`]: 'exceptionsHeaderSearchInput', placeholder: i18n.EXCEPTION_LIST_SEARCH_PLACEHOLDER, incremental: false, schema: EXCEPTIONS_SEARCH_SCHEMA, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx index 8cc3113a5706a..a3f1c8406b055 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx @@ -13,13 +13,20 @@ import { mockHistory } from '../../../../../../common/utils/route/index.test'; import { getExceptionListSchemaMock } from '../../../../../../../../lists/common/schemas/response/exception_list_schema.mock'; import { ExceptionListsTable } from './exceptions_table'; -import { useKibana } from '../../../../../../common/lib/kibana'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; import { useAllExceptionLists } from './use_all_exception_lists'; +import { useHistory } from 'react-router-dom'; jest.mock('../../../../../../common/lib/kibana'); jest.mock('./use_all_exception_lists'); jest.mock('@kbn/securitysolution-list-hooks'); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + return { + ...originalModule, + useHistory: jest.fn(), + }; +}); jest.mock('@kbn/i18n/react', () => { const originalModule = jest.requireActual('@kbn/i18n/react'); const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); @@ -29,25 +36,29 @@ jest.mock('@kbn/i18n/react', () => { FormattedRelative, }; }); + +jest.mock('../../../../../containers/detection_engine/lists/use_lists_config', () => ({ + useListsConfig: jest.fn().mockReturnValue({ loading: false }), +})); + +jest.mock('../../../../../components/user_info', () => ({ + useUserData: jest.fn().mockReturnValue([ + { + loading: false, + canUserCRUD: false, + }, + ]), +})); + describe('ExceptionListsTable', () => { const exceptionList1 = getExceptionListSchemaMock(); const exceptionList2 = { ...getExceptionListSchemaMock(), list_id: 'not_endpoint_list', id: '2' }; - beforeEach(() => { - (useKibana as jest.Mock).mockReturnValue({ - services: { - http: {}, - notifications: { - toasts: { - addError: jest.fn(), - }, - }, - timelines: { - getLastUpdated: () => null, - }, - }, - }); + beforeAll(() => { + (useHistory as jest.Mock).mockReturnValue(mockHistory); + }); + beforeEach(() => { (useApi as jest.Mock).mockReturnValue({ deleteExceptionList: jest.fn(), exportExceptionList: jest.fn(), @@ -80,15 +91,9 @@ describe('ExceptionListsTable', () => { it('renders delete option disabled if list is "endpoint_list"', async () => { const wrapper = mount( <TestProviders> - <ExceptionListsTable - history={mockHistory} - hasPermissions - loading={false} - formatUrl={jest.fn()} - /> + <ExceptionListsTable /> </TestProviders> ); - expect(wrapper.find('[data-test-subj="exceptionsTableListId"]').at(0).text()).toEqual( 'endpoint_list' ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index f38bde4839f18..b4f5efe2348bb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -12,18 +12,19 @@ import { EuiLoadingContent, EuiProgress, EuiSearchBarProps, + EuiSpacer, } from '@elastic/eui'; -import { History } from 'history'; import type { NamespaceType, ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; import { AutoDownload } from '../../../../../../common/components/auto_download/auto_download'; import { useKibana } from '../../../../../../common/lib/kibana'; -import { FormatUrl } from '../../../../../../common/components/link_to'; -import { HeaderSection } from '../../../../../../common/components/header_section'; +import { useFormatUrl } from '../../../../../../common/components/link_to'; import { Loader } from '../../../../../../common/components/loader'; import { Panel } from '../../../../../../common/components/panel'; +import { DetectionEngineHeaderPage } from '../../../../../components/detection_engine_header_page'; + import * as i18n from './translations'; import { AllRulesUtilityBar } from '../utility_bar'; import { AllExceptionListsColumns, getAllExceptionListsColumns } from './columns'; @@ -32,16 +33,13 @@ import { ReferenceErrorModal } from '../../../../../components/value_lists_manag import { patchRule } from '../../../../../containers/detection_engine/rules/api'; import { ExceptionsSearchBar } from './exceptions_search_bar'; import { getSearchFilters } from '../helpers'; +import { SecurityPageName } from '../../../../../../../common/constants'; +import { useUserData } from '../../../../../components/user_info'; +import { userHasPermissions } from '../../helpers'; +import { useListsConfig } from '../../../../../containers/detection_engine/lists/use_lists_config'; export type Func = () => Promise<void>; -interface ExceptionListsTableProps { - history: History; - hasPermissions: boolean; - loading: boolean; - formatUrl: FormatUrl; -} - interface ReferenceModalState { contentText: string; rulesReferences: string[]; @@ -58,343 +56,346 @@ const exceptionReferenceModalInitialState: ReferenceModalState = { listNamespaceType: 'single', }; -export const ExceptionListsTable = React.memo<ExceptionListsTableProps>( - ({ formatUrl, history, hasPermissions, loading }) => { - const { - services: { http, notifications, timelines }, - } = useKibana(); - const { exportExceptionList, deleteExceptionList } = useApi(http); - - const [showReferenceErrorModal, setShowReferenceErrorModal] = useState(false); - const [referenceModalState, setReferenceModalState] = useState<ReferenceModalState>( - exceptionReferenceModalInitialState - ); - const [filters, setFilters] = useState<ExceptionListFilter | undefined>(undefined); - const [loadingExceptions, exceptions, pagination, refreshExceptions] = useExceptionLists({ - errorMessage: i18n.ERROR_EXCEPTION_LISTS, - filterOptions: filters, - http, - namespaceTypes: ['single', 'agnostic'], - notifications, - showTrustedApps: false, - showEventFilters: false, - }); - const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists( - { - exceptionLists: exceptions ?? [], - } - ); - const [initLoading, setInitLoading] = useState(true); - const [lastUpdated, setLastUpdated] = useState(Date.now()); - const [deletingListIds, setDeletingListIds] = useState<string[]>([]); - const [exportingListIds, setExportingListIds] = useState<string[]>([]); - const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); - const { addError } = useAppToasts(); - - const handleDeleteSuccess = useCallback( - (listId?: string) => () => { - notifications.toasts.addSuccess({ - title: i18n.exceptionDeleteSuccessMessage(listId ?? referenceModalState.listId), - }); - }, - [notifications.toasts, referenceModalState.listId] - ); +export const ExceptionListsTable = React.memo(() => { + const { formatUrl } = useFormatUrl(SecurityPageName.rules); + const [{ loading: userInfoLoading, canUserCRUD }] = useUserData(); + const hasPermissions = userHasPermissions(canUserCRUD); + + const { loading: listsConfigLoading } = useListsConfig(); + const loading = userInfoLoading || listsConfigLoading; + + const { + services: { http, notifications, timelines, application }, + } = useKibana(); + const { exportExceptionList, deleteExceptionList } = useApi(http); + + const [showReferenceErrorModal, setShowReferenceErrorModal] = useState(false); + const [referenceModalState, setReferenceModalState] = useState<ReferenceModalState>( + exceptionReferenceModalInitialState + ); + const [filters, setFilters] = useState<ExceptionListFilter | undefined>(undefined); + const [loadingExceptions, exceptions, pagination, refreshExceptions] = useExceptionLists({ + errorMessage: i18n.ERROR_EXCEPTION_LISTS, + filterOptions: filters, + http, + namespaceTypes: ['single', 'agnostic'], + notifications, + showTrustedApps: false, + showEventFilters: false, + }); + const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({ + exceptionLists: exceptions ?? [], + }); + const [initLoading, setInitLoading] = useState(true); + const [lastUpdated, setLastUpdated] = useState(Date.now()); + const [deletingListIds, setDeletingListIds] = useState<string[]>([]); + const [exportingListIds, setExportingListIds] = useState<string[]>([]); + const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); + const { navigateToUrl } = application; + const { addError } = useAppToasts(); + + const handleDeleteSuccess = useCallback( + (listId?: string) => () => { + notifications.toasts.addSuccess({ + title: i18n.exceptionDeleteSuccessMessage(listId ?? referenceModalState.listId), + }); + }, + [notifications.toasts, referenceModalState.listId] + ); + + const handleDeleteError = useCallback( + (err: Error & { body?: { message: string } }): void => { + addError(err, { + title: i18n.EXCEPTION_DELETE_ERROR, + }); + }, + [addError] + ); + + const handleDelete = useCallback( + ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => async () => { + try { + setDeletingListIds((ids) => [...ids, id]); + if (refreshExceptions != null) { + await refreshExceptions(); + } - const handleDeleteError = useCallback( - (err: Error & { body?: { message: string } }): void => { - addError(err, { - title: i18n.EXCEPTION_DELETE_ERROR, - }); - }, - [addError] - ); + if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { + await deleteExceptionList({ + id, + namespaceType, + onError: handleDeleteError, + onSuccess: handleDeleteSuccess(listId), + }); - const handleDelete = useCallback( - ({ - id, - listId, - namespaceType, - }: { - id: string; - listId: string; - namespaceType: NamespaceType; - }) => async () => { - try { - setDeletingListIds((ids) => [...ids, id]); if (refreshExceptions != null) { - await refreshExceptions(); + refreshExceptions(); } - - if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { - await deleteExceptionList({ - id, - namespaceType, - onError: handleDeleteError, - onSuccess: handleDeleteSuccess(listId), - }); - - if (refreshExceptions != null) { - refreshExceptions(); - } - } else { - setReferenceModalState({ - contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length), - rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name), - isLoading: true, - listId: id, - listNamespaceType: namespaceType, - }); - setShowReferenceErrorModal(true); - } - // route to patch rules with associated exception list - } catch (error) { - handleDeleteError(error); - } finally { - setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]); + } else { + setReferenceModalState({ + contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length), + rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name), + isLoading: true, + listId: id, + listNamespaceType: namespaceType, + }); + setShowReferenceErrorModal(true); } - }, - [ - deleteExceptionList, - exceptionsListsRef, - handleDeleteError, - handleDeleteSuccess, - refreshExceptions, - ] - ); - - const handleExportSuccess = useCallback( - (listId: string) => (blob: Blob): void => { - setExportDownload({ name: listId, blob }); - }, - [] - ); - - const handleExportError = useCallback( - (err: Error) => { - addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); - }, - [addError] - ); - - const handleExport = useCallback( - ({ + // route to patch rules with associated exception list + } catch (error) { + handleDeleteError(error); + } finally { + setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]); + } + }, + [ + deleteExceptionList, + exceptionsListsRef, + handleDeleteError, + handleDeleteSuccess, + refreshExceptions, + ] + ); + + const handleExportSuccess = useCallback( + (listId: string) => (blob: Blob): void => { + setExportDownload({ name: listId, blob }); + }, + [] + ); + + const handleExportError = useCallback( + (err: Error) => { + addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); + }, + [addError] + ); + + const handleExport = useCallback( + ({ + id, + listId, + namespaceType, + }: { + id: string; + listId: string; + namespaceType: NamespaceType; + }) => async () => { + setExportingListIds((ids) => [...ids, id]); + await exportExceptionList({ id, listId, namespaceType, - }: { - id: string; - listId: string; - namespaceType: NamespaceType; - }) => async () => { - setExportingListIds((ids) => [...ids, id]); - await exportExceptionList({ - id, - listId, - namespaceType, - onError: handleExportError, - onSuccess: handleExportSuccess(listId), - }); - }, - [exportExceptionList, handleExportError, handleExportSuccess] + onError: handleExportError, + onSuccess: handleExportSuccess(listId), + }); + }, + [exportExceptionList, handleExportError, handleExportSuccess] + ); + + const exceptionsColumns = useMemo((): AllExceptionListsColumns[] => { + return getAllExceptionListsColumns(handleExport, handleDelete, formatUrl, navigateToUrl); + }, [handleExport, handleDelete, formatUrl, navigateToUrl]); + + const handleRefresh = useCallback((): void => { + if (refreshExceptions != null) { + setLastUpdated(Date.now()); + refreshExceptions(); + } + }, [refreshExceptions]); + + useEffect(() => { + if (initLoading && !loading && !loadingExceptions && !loadingTableInfo) { + setInitLoading(false); + } + }, [initLoading, loading, loadingExceptions, loadingTableInfo]); + + const emptyPrompt = useMemo((): JSX.Element => { + return ( + <EuiEmptyPrompt + title={<h3>{i18n.NO_EXCEPTION_LISTS}</h3>} + titleSize="xs" + body={i18n.NO_LISTS_BODY} + /> ); - - const exceptionsColumns = useMemo((): AllExceptionListsColumns[] => { - return getAllExceptionListsColumns(handleExport, handleDelete, history, formatUrl); - }, [handleExport, handleDelete, history, formatUrl]); - - const handleRefresh = useCallback((): void => { - if (refreshExceptions != null) { - setLastUpdated(Date.now()); - refreshExceptions(); - } - }, [refreshExceptions]); - - useEffect(() => { - if (initLoading && !loading && !loadingExceptions && !loadingTableInfo) { - setInitLoading(false); - } - }, [initLoading, loading, loadingExceptions, loadingTableInfo]); - - const emptyPrompt = useMemo((): JSX.Element => { - return ( - <EuiEmptyPrompt - title={<h3>{i18n.NO_EXCEPTION_LISTS}</h3>} - titleSize="xs" - body={i18n.NO_LISTS_BODY} - /> - ); - }, []); - - const handleSearch = useCallback( - async ({ + }, []); + + const handleSearch = useCallback( + async ({ + query, + queryText, + }: Parameters<NonNullable<EuiSearchBarProps['onChange']>>[0]): Promise<void> => { + const filterOptions = { + name: null, + list_id: null, + created_by: null, + type: null, + tags: null, + }; + const searchTerms = getSearchFilters({ + defaultSearchTerm: 'name', + filterOptions, query, - queryText, - }: Parameters<NonNullable<EuiSearchBarProps['onChange']>>[0]): Promise<void> => { - const filterOptions = { - name: null, - list_id: null, - created_by: null, - type: null, - tags: null, - }; - const searchTerms = getSearchFilters({ - defaultSearchTerm: 'name', - filterOptions, - query, - searchValue: queryText, - }); - setFilters(searchTerms); - }, - [] - ); + searchValue: queryText, + }); + setFilters(searchTerms); + }, + [] + ); + + const handleCloseReferenceErrorModal = useCallback((): void => { + setDeletingListIds([]); + setShowReferenceErrorModal(false); + setReferenceModalState({ + contentText: '', + rulesReferences: [], + isLoading: false, + listId: '', + listNamespaceType: 'single', + }); + }, []); + + const handleReferenceDelete = useCallback(async (): Promise<void> => { + const exceptionListId = referenceModalState.listId; + const exceptionListNamespaceType = referenceModalState.listNamespaceType; + const relevantRules = exceptionsListsRef[exceptionListId].rules; + + try { + await Promise.all( + relevantRules.map((rule) => { + const abortCtrl = new AbortController(); + const exceptionLists = (rule.exceptions_list ?? []).filter( + ({ id }) => id !== exceptionListId + ); + + return patchRule({ + ruleProperties: { + rule_id: rule.rule_id, + exceptions_list: exceptionLists, + }, + signal: abortCtrl.signal, + }); + }) + ); - const handleCloseReferenceErrorModal = useCallback((): void => { + await deleteExceptionList({ + id: exceptionListId, + namespaceType: exceptionListNamespaceType, + onError: handleDeleteError, + onSuccess: handleDeleteSuccess(), + }); + } catch (err) { + handleDeleteError(err); + } finally { + setReferenceModalState(exceptionReferenceModalInitialState); setDeletingListIds([]); setShowReferenceErrorModal(false); - setReferenceModalState({ - contentText: '', - rulesReferences: [], - isLoading: false, - listId: '', - listNamespaceType: 'single', - }); - }, []); - - const handleReferenceDelete = useCallback(async (): Promise<void> => { - const exceptionListId = referenceModalState.listId; - const exceptionListNamespaceType = referenceModalState.listNamespaceType; - const relevantRules = exceptionsListsRef[exceptionListId].rules; - - try { - await Promise.all( - relevantRules.map((rule) => { - const abortCtrl = new AbortController(); - const exceptionLists = (rule.exceptions_list ?? []).filter( - ({ id }) => id !== exceptionListId - ); - - return patchRule({ - ruleProperties: { - rule_id: rule.rule_id, - exceptions_list: exceptionLists, - }, - signal: abortCtrl.signal, - }); - }) - ); - - await deleteExceptionList({ - id: exceptionListId, - namespaceType: exceptionListNamespaceType, - onError: handleDeleteError, - onSuccess: handleDeleteSuccess(), - }); - } catch (err) { - handleDeleteError(err); - } finally { - setReferenceModalState(exceptionReferenceModalInitialState); - setDeletingListIds([]); - setShowReferenceErrorModal(false); - if (refreshExceptions != null) { - refreshExceptions(); - } + if (refreshExceptions != null) { + refreshExceptions(); } - }, [ - referenceModalState.listId, - referenceModalState.listNamespaceType, - exceptionsListsRef, - deleteExceptionList, - handleDeleteError, - handleDeleteSuccess, - refreshExceptions, - ]); - - const paginationMemo = useMemo( - () => ({ - pageIndex: pagination.page - 1, - pageSize: pagination.perPage, - totalItemCount: pagination.total || 0, - pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], - }), - [pagination] - ); - - const handleOnDownload = useCallback(() => { - setExportDownload({}); - }, []); - - const tableItems = (exceptionListsWithRuleRefs ?? []).map((item) => ({ - ...item, - isDeleting: deletingListIds.includes(item.id), - isExporting: exportingListIds.includes(item.id), - })); - - return ( - <> - <Panel loading={!initLoading && loadingTableInfo} data-test-subj="allExceptionListsPanel"> - <> - {loadingTableInfo && ( - <EuiProgress - data-test-subj="loadingRulesInfoProgress" - size="xs" - position="absolute" - color="accent" + } + }, [ + referenceModalState.listId, + referenceModalState.listNamespaceType, + exceptionsListsRef, + deleteExceptionList, + handleDeleteError, + handleDeleteSuccess, + refreshExceptions, + ]); + + const paginationMemo = useMemo( + () => ({ + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: pagination.total || 0, + pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], + }), + [pagination] + ); + + const handleOnDownload = useCallback(() => { + setExportDownload({}); + }, []); + + const tableItems = (exceptionListsWithRuleRefs ?? []).map((item) => ({ + ...item, + isDeleting: deletingListIds.includes(item.id), + isExporting: exportingListIds.includes(item.id), + })); + + return ( + <> + <DetectionEngineHeaderPage + title={i18n.ALL_EXCEPTIONS} + subtitle={timelines.getLastUpdated({ showUpdating: loading, updatedAt: lastUpdated })} + /> + <Panel loading={!initLoading && loadingTableInfo} data-test-subj="allExceptionListsPanel"> + <> + {loadingTableInfo && ( + <EuiProgress + data-test-subj="loadingRulesInfoProgress" + size="xs" + position="absolute" + color="accent" + /> + )} + {!initLoading && <ExceptionsSearchBar onSearch={handleSearch} />} + <EuiSpacer size="m" /> + + {loadingTableInfo && !initLoading && !showReferenceErrorModal && ( + <Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" /> + )} + + {initLoading ? ( + <EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} /> + ) : ( + <> + <AllRulesUtilityBar + showBulkActions={false} + canBulkEdit={hasPermissions} + paginationTotal={exceptionListsWithRuleRefs.length ?? 0} + numberSelectedItems={0} + onRefresh={handleRefresh} /> - )} - <HeaderSection - split - title={i18n.ALL_EXCEPTIONS} - subtitle={timelines.getLastUpdated({ showUpdating: loading, updatedAt: lastUpdated })} - > - {!initLoading && <ExceptionsSearchBar onSearch={handleSearch} />} - </HeaderSection> - - {loadingTableInfo && !initLoading && !showReferenceErrorModal && ( - <Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" /> - )} - - {initLoading ? ( - <EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} /> - ) : ( - <> - <AllRulesUtilityBar - showBulkActions={false} - canBulkEdit={hasPermissions} - paginationTotal={exceptionListsWithRuleRefs.length ?? 0} - numberSelectedItems={0} - onRefresh={handleRefresh} - /> - <EuiBasicTable - data-test-subj="exceptions-table" - columns={exceptionsColumns} - isSelectable={hasPermissions} - itemId="id" - items={tableItems} - noItemsMessage={emptyPrompt} - onChange={() => {}} - pagination={paginationMemo} - /> - </> - )} - </> - </Panel> - <AutoDownload - blob={exportDownload.blob} - name={`${exportDownload.name}.ndjson`} - onDownload={handleOnDownload} - /> - <ReferenceErrorModal - cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON} - confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON} - contentText={referenceModalState.contentText} - onCancel={handleCloseReferenceErrorModal} - onClose={handleCloseReferenceErrorModal} - onConfirm={handleReferenceDelete} - references={referenceModalState.rulesReferences} - showModal={showReferenceErrorModal} - titleText={i18n.REFERENCE_MODAL_TITLE} - /> - </> - ); - } -); + <EuiBasicTable + data-test-subj="exceptions-table" + columns={exceptionsColumns} + isSelectable={hasPermissions} + itemId="id" + items={tableItems} + noItemsMessage={emptyPrompt} + onChange={() => {}} + pagination={paginationMemo} + /> + </> + )} + </> + </Panel> + <AutoDownload + blob={exportDownload.blob} + name={`${exportDownload.name}.ndjson`} + onDownload={handleOnDownload} + /> + <ReferenceErrorModal + cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON} + confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON} + contentText={referenceModalState.contentText} + onCancel={handleCloseReferenceErrorModal} + onClose={handleCloseReferenceErrorModal} + onConfirm={handleReferenceDelete} + references={referenceModalState.rulesReferences} + showModal={showReferenceErrorModal} + titleText={i18n.REFERENCE_MODAL_TITLE} + /> + </> + ); +}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index 9597c221843be..200bf0c719320 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -326,33 +326,4 @@ describe('AllRules', () => { expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeFalsy(); }); }); - - it('renders exceptions lists tab when tab clicked', async () => { - const wrapper = mount( - <TestProviders> - <AllRules - createPrePackagedRules={jest.fn()} - hasPermissions - loading={false} - loadingCreatePrePackagedRules={false} - refetchPrePackagedRulesStatus={jest.fn()} - rulesCustomInstalled={1} - rulesInstalled={0} - rulesNotInstalled={0} - rulesNotUpdated={0} - setRefreshRulesData={jest.fn()} - /> - </TestProviders> - ); - - await waitFor(() => { - const exceptionsTab = wrapper.find('[data-test-subj="allRulesTableTab-exceptions"] button'); - exceptionsTab.simulate('click'); - - wrapper.update(); - expect(wrapper.exists('[data-test-subj="allExceptionListsPanel"]')).toBeTruthy(); - expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeFalsy(); - expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeFalsy(); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx index 144aa05cb3b07..76a049936a722 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx @@ -14,7 +14,6 @@ import { useFormatUrl } from '../../../../../common/components/link_to'; import { CreatePreBuiltRules } from '../../../../containers/detection_engine/rules'; import { RulesTables } from './rules_tables'; import * as i18n from '../translations'; -import { ExceptionListsTable } from './exceptions/exceptions_table'; interface AllRulesProps { createPrePackagedRules: CreatePreBuiltRules | null; @@ -46,13 +45,7 @@ const allRulesTabs = [ name: i18n.MONITORING_TAB, disabled: false, }, - { - id: AllRulesTabs.exceptions, - name: i18n.EXCEPTIONS_TAB, - disabled: false, - }, ]; - /** * Table Component for displaying all Rules for a given cluster. Provides the ability to filter * by name, sort by enabled, and perform the following actions: @@ -75,7 +68,7 @@ export const AllRules = React.memo<AllRulesProps>( setRefreshRulesData, }) => { const history = useHistory(); - const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { formatUrl } = useFormatUrl(SecurityPageName.rules); const [allRulesTab, setAllRulesTab] = useState(AllRulesTabs.rules); const tabs = useMemo( @@ -100,35 +93,23 @@ export const AllRules = React.memo<AllRulesProps>( return ( <> - <EuiSpacer /> {tabs} <EuiSpacer /> - - {(allRulesTab === AllRulesTabs.rules || allRulesTab === AllRulesTabs.monitoring) && ( - <RulesTables - history={history} - formatUrl={formatUrl} - selectedTab={allRulesTab} - createPrePackagedRules={createPrePackagedRules} - hasPermissions={hasPermissions} - loading={loading} - loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} - refetchPrePackagedRulesStatus={refetchPrePackagedRulesStatus} - rulesCustomInstalled={rulesCustomInstalled} - rulesInstalled={rulesInstalled} - rulesNotInstalled={rulesNotInstalled} - rulesNotUpdated={rulesNotUpdated} - setRefreshRulesData={setRefreshRulesData} - /> - )} - {allRulesTab === AllRulesTabs.exceptions && ( - <ExceptionListsTable - formatUrl={formatUrl} - history={history} - hasPermissions={hasPermissions} - loading={loading} - /> - )} + <RulesTables + history={history} + formatUrl={formatUrl} + selectedTab={allRulesTab} + createPrePackagedRules={createPrePackagedRules} + hasPermissions={hasPermissions} + loading={loading} + loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} + refetchPrePackagedRulesStatus={refetchPrePackagedRulesStatus} + rulesCustomInstalled={rulesCustomInstalled} + rulesInstalled={rulesInstalled} + rulesNotInstalled={rulesNotInstalled} + rulesNotUpdated={rulesNotUpdated} + setRefreshRulesData={setRefreshRulesData} + /> </> ); } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 2ec34aaece60b..77ca5be0c0ac1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -148,6 +148,7 @@ export const RulesTables = React.memo<RulesTableProps>( const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const [, dispatchToaster] = useStateToaster(); const mlCapabilities = useMlCapabilities(); + const { navigateToApp } = useKibana().services.application; // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); @@ -279,6 +280,7 @@ export const RulesTables = React.memo<RulesTableProps>( (loadingRulesAction === 'enable' || loadingRulesAction === 'disable') ? loadingRuleIds : [], + navigateToApp, reFetchRules, refetchPrePackagedRulesStatus, hasReadActionsPrivileges: hasActionsPrivileges, @@ -294,6 +296,7 @@ export const RulesTables = React.memo<RulesTableProps>( history, loadingRuleIds, loadingRulesAction, + navigateToApp, reFetchRules, ]); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx index 9622610f3c637..45713b6b0667f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx @@ -25,7 +25,7 @@ jest.mock('react-router-dom', () => { }), }; }); - +jest.mock('../../../../../common/lib/kibana'); jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 23edf785a7f3a..3d488f1f08c98 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -14,7 +14,6 @@ import { EuiFlexGroup, } from '@elastic/eui'; import React, { useCallback, useRef, useState, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; import styled, { StyledComponent } from 'styled-components'; import { useCreateRule } from '../../../../containers/detection_engine/rules'; @@ -48,6 +47,8 @@ import { formatRule, stepIsValid } from './helpers'; import * as i18n from './translations'; import { SecurityPageName } from '../../../../../app/types'; import { ruleStepsOrder } from '../utils'; +import { APP_ID } from '../../../../../../common/constants'; +import { useKibana } from '../../../../../common/lib/kibana'; const formHookNoop = async (): Promise<undefined> => undefined; @@ -100,6 +101,7 @@ const CreateRulePageComponent: React.FC = () => { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, } = useListsConfig(); + const { navigateToApp } = useKibana().services.application; const loading = userInfoLoading || listsConfigLoading; const [, dispatchToaster] = useStateToaster(); const [activeStep, setActiveStep] = useState<RuleStep>(RuleStep.defineRule); @@ -143,7 +145,6 @@ const CreateRulePageComponent: React.FC = () => { const ruleType = stepsData.current[RuleStep.defineRule].data?.ruleType; const ruleName = stepsData.current[RuleStep.aboutRule].data?.name; const actionMessageParams = useMemo(() => getActionMessageParams(ruleType), [ruleType]); - const history = useHistory(); const handleAccordionToggle = useCallback( (step: RuleStep, isOpen: boolean) => @@ -235,6 +236,10 @@ const CreateRulePageComponent: React.FC = () => { [activeStep] ); + const submitStepDefineRule = useCallback(() => { + submitStep(RuleStep.defineRule); + }, [submitStep]); + const defineRuleButton = ( <AccordionTitle name="1" @@ -266,7 +271,10 @@ const CreateRulePageComponent: React.FC = () => { if (ruleName && ruleId) { displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); - history.replace(getRuleDetailsUrl(ruleId)); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId), + }); return null; } @@ -278,13 +286,18 @@ const CreateRulePageComponent: React.FC = () => { needsListsConfiguration ) ) { - history.replace(getDetectionEngineUrl()); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, + path: getDetectionEngineUrl(), + }); return null; } else if (!userHasPermissions(canUserCRUD)) { - history.replace(getRulesUrl()); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRulesUrl(), + }); return null; } - return ( <> <SecuritySolutionPageWrapper> @@ -294,7 +307,7 @@ const CreateRulePageComponent: React.FC = () => { backOptions={{ href: getRulesUrl(), text: i18n.BACK_TO_RULES, - pageId: SecurityPageName.detections, + pageId: SecurityPageName.rules, }} isLoading={isLoading || loading} title={i18n.PAGE_TITLE} @@ -327,7 +340,7 @@ const CreateRulePageComponent: React.FC = () => { isReadOnlyView={activeStep !== RuleStep.defineRule} isLoading={isLoading || loading} setForm={setFormHook} - onSubmit={() => submitStep(RuleStep.defineRule)} + onSubmit={submitStepDefineRule} descriptionColumns="singleSplit" /> </StepDefineRuleAccordion> @@ -437,7 +450,7 @@ const CreateRulePageComponent: React.FC = () => { </EuiFlexGroup> </SecuritySolutionPageWrapper> - <SpyRoute pageName={SecurityPageName.detections} /> + <SpyRoute pageName={SecurityPageName.rules} /> </> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 92679cb2662d7..a7a9b31d1f408 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -22,7 +22,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -86,7 +86,7 @@ import { SecurityPageName } from '../../../../../app/types'; import { LinkButton } from '../../../../../common/components/links'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer'; -import { DEFAULT_INDEX_PATTERN } from '../../../../../../common/constants'; +import { APP_ID, DEFAULT_INDEX_PATTERN } from '../../../../../../common/constants'; import { useGlobalFullScreen } from '../../../../../common/containers/use_full_screen'; import { Display } from '../../../../../hosts/pages/display'; @@ -153,6 +153,7 @@ const ruleDetailTabs = [ ]; const RuleDetailsPageComponent = () => { + const { navigateToApp } = useKibana().services.application; const dispatch = useDispatch(); const containerElement = useRef<HTMLDivElement | null>(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -220,8 +221,7 @@ const RuleDetailsPageComponent = () => { const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const mlCapabilities = useMlCapabilities(); - const history = useHistory(); - const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { formatUrl } = useFormatUrl(SecurityPageName.rules); const { globalFullScreen } = useGlobalFullScreen(); // TODO: Once we are past experimental phase this code should be removed @@ -442,9 +442,12 @@ const RuleDetailsPageComponent = () => { const goToEditRule = useCallback( (ev) => { ev.preventDefault(); - history.push(getEditRuleUrl(ruleId ?? '')); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getEditRuleUrl(ruleId ?? ''), + }); }, - [history, ruleId] + [navigateToApp, ruleId] ); const editRule = useMemo(() => { @@ -548,7 +551,10 @@ const RuleDetailsPageComponent = () => { needsListsConfiguration ) ) { - history.replace(getDetectionEngineUrl()); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, + path: getDetectionEngineUrl(), + }); return null; } @@ -574,7 +580,7 @@ const RuleDetailsPageComponent = () => { backOptions={{ href: getRulesUrl(), text: i18n.BACK_TO_RULES, - pageId: SecurityPageName.detections, + pageId: SecurityPageName.rules, dataTestSubj: 'ruleDetailsBackToAllRules', }} border @@ -744,7 +750,7 @@ const RuleDetailsPageComponent = () => { </SecuritySolutionPageWrapper> )} - <SpyRoute pageName={SecurityPageName.detections} state={{ ruleName: rule?.name }} /> + <SpyRoute pageName={SecurityPageName.rules} state={{ ruleName: rule?.name }} /> </> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx index e7cdfbe268fe6..e56742b9f0a51 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx @@ -16,6 +16,7 @@ import { useParams } from 'react-router-dom'; import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; +jest.mock('../../../../../common/lib/kibana'); jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index 41710a822e539..4786d7f2eae78 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { UpdateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; import { useRule, useUpdateRule } from '../../../../containers/detection_engine/rules'; @@ -55,11 +55,12 @@ import { RuleStep, RuleStepsFormHooks, RuleStepsFormData, RuleStepsData } from ' import * as i18n from './translations'; import { SecurityPageName } from '../../../../../app/types'; import { ruleStepsOrder } from '../utils'; +import { useKibana } from '../../../../../common/lib/kibana'; +import { APP_ID } from '../../../../../../common/constants'; const formHookNoop = async (): Promise<undefined> => undefined; const EditRulePageComponent: FC = () => { - const history = useHistory(); const [, dispatchToaster] = useStateToaster(); const [ { @@ -74,6 +75,8 @@ const EditRulePageComponent: FC = () => { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration, } = useListsConfig(); + const { navigateToApp } = useKibana().services.application; + const { detailName: ruleId } = useParams<{ detailName: string | undefined }>(); const [ruleLoading, rule] = useRule(ruleId); const loading = ruleLoading || userInfoLoading || listsConfigLoading; @@ -299,9 +302,12 @@ const EditRulePageComponent: FC = () => { const goToDetailsRule = useCallback( (ev) => { ev.preventDefault(); - history.replace(getRuleDetailsUrl(ruleId ?? '')); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId ?? ''), + }); }, - [history, ruleId] + [navigateToApp, ruleId] ); useEffect(() => { @@ -314,7 +320,10 @@ const EditRulePageComponent: FC = () => { if (isSaved) { displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); - history.replace(getRuleDetailsUrl(ruleId ?? '')); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId ?? ''), + }); return null; } @@ -326,10 +335,16 @@ const EditRulePageComponent: FC = () => { needsListsConfiguration ) ) { - history.replace(getDetectionEngineUrl()); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, + path: getDetectionEngineUrl(), + }); return null; } else if (!userHasPermissions(canUserCRUD)) { - history.replace(getRuleDetailsUrl(ruleId ?? '')); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId ?? ''), + }); return null; } @@ -342,7 +357,7 @@ const EditRulePageComponent: FC = () => { backOptions={{ href: getRuleDetailsUrl(ruleId ?? ''), text: `${i18n.BACK_TO} ${rule?.name ?? ''}`, - pageId: SecurityPageName.detections, + pageId: SecurityPageName.rules, dataTestSubj: 'ruleEditBackToRuleDetails', }} isLoading={isLoading} @@ -412,7 +427,7 @@ const EditRulePageComponent: FC = () => { </EuiFlexGroup> </SecuritySolutionPageWrapper> - <SpyRoute pageName={SecurityPageName.detections} state={{ ruleName: rule?.name }} /> + <SpyRoute pageName={SecurityPageName.rules} state={{ ruleName: rule?.name }} /> </> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx index bcd5ccdc0b5ac..550a289a11a97 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx @@ -30,6 +30,8 @@ jest.mock('react-router-dom', () => { jest.mock('../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../common/components/link_to'); jest.mock('../../../components/user_info'); + +jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/components/toasters', () => { const actual = jest.requireActual('../../../../common/components/toasters'); return { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index 29fd8e2e8b247..f957f77ac4c1a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -7,7 +7,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { useHistory } from 'react-router-dom'; import { usePrePackagedRules, importRules } from '../../../containers/detection_engine/rules'; import { useListsConfig } from '../../../containers/detection_engine/lists/use_lists_config'; @@ -37,14 +36,17 @@ import { useFormatUrl } from '../../../../common/components/link_to'; import { NeedAdminForUpdateRulesCallOut } from '../../../components/callouts/need_admin_for_update_callout'; import { MlJobCompatibilityCallout } from '../../../components/callouts/ml_job_compatibility_callout'; import { MissingPrivilegesCallOut } from '../../../components/callouts/missing_privileges_callout'; +import { APP_ID } from '../../../../../common/constants'; +import { useKibana } from '../../../../common/lib/kibana'; type Func = () => Promise<void>; const RulesPageComponent: React.FC = () => { - const history = useHistory(); const [showImportModal, setShowImportModal] = useState(false); const [showValueListsModal, setShowValueListsModal] = useState(false); const refreshRulesData = useRef<null | Func>(null); + const { navigateToApp } = useKibana().services.application; + const [ { loading: userInfoLoading, @@ -93,7 +95,7 @@ const RulesPageComponent: React.FC = () => { timelinesNotInstalled, timelinesNotUpdated ); - const { formatUrl } = useFormatUrl(SecurityPageName.detections); + const { formatUrl } = useFormatUrl(SecurityPageName.rules); const handleRefreshRules = useCallback(async () => { if (refreshRulesData.current != null) { @@ -123,9 +125,9 @@ const RulesPageComponent: React.FC = () => { const goToNewRule = useCallback( (ev) => { ev.preventDefault(); - history.push(getCreateRuleUrl()); + navigateToApp(APP_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); }, - [history] + [navigateToApp] ); const loadPrebuiltRulesAndTemplatesButton = useMemo( @@ -154,7 +156,10 @@ const RulesPageComponent: React.FC = () => { needsListsConfiguration ) ) { - history.replace(getDetectionEngineUrl()); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, + path: getDetectionEngineUrl(), + }); return null; } @@ -183,14 +188,7 @@ const RulesPageComponent: React.FC = () => { title={i18n.IMPORT_RULE} /> <SecuritySolutionPageWrapper> - <DetectionEngineHeaderPage - backOptions={{ - href: getDetectionEngineUrl(), - text: i18n.BACK_TO_DETECTIONS, - pageId: SecurityPageName.detections, - }} - title={i18n.PAGE_TITLE} - > + <DetectionEngineHeaderPage title={i18n.PAGE_TITLE}> <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}> {loadPrebuiltRulesAndTemplatesButton && ( <EuiFlexItem grow={false}>{loadPrebuiltRulesAndTemplatesButton}</EuiFlexItem> @@ -260,7 +258,7 @@ const RulesPageComponent: React.FC = () => { /> </SecuritySolutionPageWrapper> - <SpyRoute pageName={SecurityPageName.detections} /> + <SpyRoute pageName={SecurityPageName.rules} /> </> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index defd976a04c4b..8379bbcb590e1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -44,7 +44,7 @@ export const ADD_NEW_RULE = i18n.translate( ); export const PAGE_TITLE = i18n.translate('xpack.securitySolution.detectionEngine.rules.pageTitle', { - defaultMessage: 'Detection rules', + defaultMessage: 'Rules', }); export const ADD_PAGE_TITLE = i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.test.ts index 217f786cb0a6a..d405837a4f7f2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.test.ts @@ -11,7 +11,7 @@ const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: b `${appId}${options?.path ?? ''}`; describe('getBreadcrumbs', () => { - it('returns default value for incorrect params', () => { + it('Does not render for incorrect params', () => { expect( getBreadcrumbs( { @@ -24,6 +24,6 @@ describe('getBreadcrumbs', () => { [], getUrlForAppMock ) - ).toEqual([{ href: 'securitySolution:detections', text: 'Detections' }]); + ).toEqual([]); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 421ca6209e0af..bbc085eaa0be8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -9,18 +9,16 @@ import { isEmpty } from 'lodash/fp'; import { ChromeBreadcrumb } from '../../../../../../../../src/core/public'; import { - getDetectionEngineTabUrl, getRulesUrl, getRuleDetailsUrl, getCreateRuleUrl, getEditRuleUrl, } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import * as i18nDetections from '../translations'; import * as i18nRules from './translations'; import { RouteSpyState } from '../../../../common/utils/route/types'; import { GetUrlForApp } from '../../../../common/components/navigation/types'; import { SecurityPageName } from '../../../../app/types'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_ID, RULES_PATH } from '../../../../../common/constants'; import { RuleStep, RuleStepsOrder } from './types'; export const ruleStepsOrder: RuleStepsOrder = [ @@ -30,22 +28,14 @@ export const ruleStepsOrder: RuleStepsOrder = [ RuleStep.ruleActions, ]; -const getTabBreadcrumb = (pathname: string, search: string[], getUrlForApp: GetUrlForApp) => { +const getRulesBreadcrumb = (pathname: string, search: string[], getUrlForApp: GetUrlForApp) => { const tabPath = pathname.split('/')[1]; - if (tabPath === 'alerts') { - return { - text: i18nDetections.ALERT, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { - path: getDetectionEngineTabUrl(tabPath, !isEmpty(search[0]) ? search[0] : ''), - }), - }; - } - if (tabPath === 'rules') { return { text: i18nRules.PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getRulesUrl(!isEmpty(search[0]) ? search[0] : ''), }), }; @@ -53,29 +43,22 @@ const getTabBreadcrumb = (pathname: string, search: string[], getUrlForApp: GetU }; const isRuleCreatePage = (pathname: string) => - pathname.includes('/rules') && pathname.includes('/create'); + pathname.includes(RULES_PATH) && pathname.includes('/create'); const isRuleEditPage = (pathname: string) => - pathname.includes('/rules') && pathname.includes('/edit'); + pathname.includes(RULES_PATH) && pathname.includes('/edit'); export const getBreadcrumbs = ( params: RouteSpyState, search: string[], getUrlForApp: GetUrlForApp ): ChromeBreadcrumb[] => { - let breadcrumb = [ - { - text: i18nDetections.BREADCRUMB_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { - path: !isEmpty(search[0]) ? search[0] : '', - }), - }, - ]; + let breadcrumb: ChromeBreadcrumb[] = []; - const tabBreadcrumb = getTabBreadcrumb(params.pathName, search, getUrlForApp); + const rulesBreadcrumb = getRulesBreadcrumb(params.pathName, search, getUrlForApp); - if (tabBreadcrumb) { - breadcrumb = [...breadcrumb, tabBreadcrumb]; + if (rulesBreadcrumb) { + breadcrumb = [...breadcrumb, rulesBreadcrumb]; } if (params.detailName && params.state?.ruleName) { @@ -83,7 +66,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.state.ruleName, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), }), }, @@ -95,7 +79,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: i18nRules.ADD_PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl(!isEmpty(search[0]) ? search[0] : ''), }), }, @@ -107,7 +92,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: i18nRules.EDIT_PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getEditRuleUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), }), }, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts index 4b07369ae3039..9475701baf2fd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/translations.ts @@ -17,7 +17,7 @@ export const BREADCRUMB_TITLE = i18n.translate( export const PAGE_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.detectionsPageTitle', { - defaultMessage: 'Detection alerts', + defaultMessage: 'Alerts', } ); @@ -37,7 +37,7 @@ export const SIGNAL = i18n.translate('xpack.securitySolution.detectionEngine.sig }); export const ALERT = i18n.translate('xpack.securitySolution.detectionEngine.alertTitle', { - defaultMessage: 'Detection alerts', + defaultMessage: 'Alerts', }); export const BUTTON_MANAGE_RULES = i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/routes.tsx b/x-pack/plugins/security_solution/public/detections/routes.tsx index e91c612bc49ab..329e1c939c201 100644 --- a/x-pack/plugins/security_solution/public/detections/routes.tsx +++ b/x-pack/plugins/security_solution/public/detections/routes.tsx @@ -6,18 +6,35 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Redirect, RouteProps, RouteComponentProps } from 'react-router-dom'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { ALERTS_PATH, DETECTIONS_PATH, SecurityPageName } from '../../common/constants'; -import { DetectionEngineContainer } from './pages/detection_engine'; -import { NotFoundPage } from '../app/404'; +import { SpyRoute } from '../common/utils/route/spy_routes'; -export const AlertsRoutes: React.FC = () => ( - <Switch> - <Route path="/"> - <DetectionEngineContainer /> - </Route> - <Route> - <NotFoundPage /> - </Route> - </Switch> +import { DetectionEnginePage } from './pages/detection_engine/detection_engine'; + +const renderAlertsRoutes = () => ( + <TrackApplicationView viewId={SecurityPageName.alerts}> + <DetectionEnginePage /> + <SpyRoute pageName={SecurityPageName.alerts} /> + </TrackApplicationView> ); + +const DetectionsRedirects = ({ location }: RouteComponentProps) => + location.pathname === DETECTIONS_PATH ? ( + <Redirect to={{ ...location, pathname: ALERTS_PATH }} /> + ) : ( + <Redirect to={{ ...location, pathname: location.pathname.replace(DETECTIONS_PATH, '') }} /> + ); + +export const routes: RouteProps[] = [ + { + path: ALERTS_PATH, + render: renderAlertsRoutes, + }, + { + path: DETECTIONS_PATH, + component: DetectionsRedirects, + }, +]; diff --git a/x-pack/plugins/security_solution/public/exceptions/index.ts b/x-pack/plugins/security_solution/public/exceptions/index.ts new file mode 100644 index 0000000000000..eccb2ba7578c7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; + +import { SecuritySubPlugin } from '../app/types'; +import { DETECTIONS_TIMELINE_IDS } from '../detections'; +import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; +import { routes } from './routes'; + +export class Exceptions { + public setup() {} + + public start(storage: Storage): SecuritySubPlugin { + return { + storageTimelines: { + timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), + }, + routes, + }; + } +} diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx new file mode 100644 index 0000000000000..0afc152ed3870 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; + +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants'; +import { ExceptionListsTable } from '../detections/pages/detection_engine/rules/all/exceptions/exceptions_table'; +import { SpyRoute } from '../common/utils/route/spy_routes'; + +export const ExceptionsRoutes = () => { + return ( + <TrackApplicationView viewId={SecurityPageName.exceptions}> + <ExceptionListsTable /> + <SpyRoute pageName={SecurityPageName.exceptions} /> + </TrackApplicationView> + ); +}; + +export const routes = [ + { + path: EXCEPTIONS_PATH, + render: ExceptionsRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts index e12b2f7fc37f8..d941550fdb2e3 100644 --- a/x-pack/plugins/security_solution/public/helpers.ts +++ b/x-pack/plugins/security_solution/public/helpers.ts @@ -6,9 +6,10 @@ */ import { isEmpty } from 'lodash/fp'; +import { matchPath } from 'react-router-dom'; import { CoreStart } from '../../../../src/core/public'; -import { APP_ID } from '../common/constants'; +import { ALERTS_PATH, APP_ID, EXCEPTIONS_PATH, RULES_PATH } from '../common/constants'; import { FactoryQueryTypes, StrategyResponseType, @@ -47,61 +48,85 @@ export const parseRoute = (location: Pick<Location, 'hash' | 'pathname' | 'searc export const manageOldSiemRoutes = async (coreStart: CoreStart) => { const { application } = coreStart; - const { pageName, path, search } = parseRoute(window.location); + const { pageName, path } = parseRoute(window.location); switch (pageName) { case SecurityPageName.overview: - application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.overview, replace: true, path, }); break; case 'ml-hosts': - application.navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, replace: true, path: `/ml-hosts${path}`, }); break; case SecurityPageName.hosts: - application.navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, replace: true, path, }); break; case 'ml-network': - application.navigateToApp(`${APP_ID}:${SecurityPageName.network}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.network, replace: true, path: `/ml-network${path}`, }); break; case SecurityPageName.network: - application.navigateToApp(`${APP_ID}:${SecurityPageName.network}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.network, replace: true, path, }); break; case SecurityPageName.timelines: - application.navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, replace: true, path, }); break; case SecurityPageName.case: - application.navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, replace: true, path, }); break; - case 'detections': - application.navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + case SecurityPageName.detections: + case SecurityPageName.alerts: + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.alerts, + replace: true, + path, + }); + break; + case SecurityPageName.rules: + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, + replace: true, + path, + }); + break; + case SecurityPageName.exceptions: + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.exceptions, replace: true, path, }); break; default: - application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.overview, replace: true, - path: `${search}`, + path, }); break; } @@ -115,3 +140,10 @@ export const getInspectResponse = <T extends FactoryQueryTypes>( response: response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, }); + +export const isDetectionsPath = (pathname: string): boolean => { + return !!matchPath(pathname, { + path: `(${ALERTS_PATH}|${RULES_PATH}|${EXCEPTIONS_PATH})`, + strict: false, + }); +}; diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts index 64b805bf5e2d6..cbb539f8e4107 100644 --- a/x-pack/plugins/security_solution/public/hosts/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/index.ts @@ -9,7 +9,7 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline'; import { SecuritySubPluginWithStore } from '../app/types'; import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; -import { HostsRoutes } from './routes'; +import { routes } from './routes'; import { initialHostsState, hostsReducer, HostsState } from './store'; const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [ @@ -22,7 +22,7 @@ export class Hosts { public start(storage: Storage): SecuritySubPluginWithStore<'hosts', HostsState> { return { - SubPluginRoutes: HostsRoutes, + routes, storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, HOST_TIMELINE_IDS), }, diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index b51e20b801f40..3b76ec8a0d13f 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -80,7 +80,7 @@ describe('body', () => { test(`it should pass expected object properties to ${componentName}`, () => { const wrapper = mount( <TestProviders> - <MemoryRouter initialEntries={[`/host-1/${path}`]}> + <MemoryRouter initialEntries={[`/hosts/host-1/${path}`]}> <HostDetailsTabs isInitializing={false} detailName={'host-1'} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx index 2f6d5e5bcdc33..02f8fa740c024 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/nav_tabs.tsx @@ -9,10 +9,10 @@ import { omit } from 'lodash/fp'; import * as i18n from '../translations'; import { HostDetailsNavTab } from './types'; import { HostsTableType } from '../../store/model'; -import { SecurityPageName } from '../../../app/types'; +import { HOSTS_PATH } from '../../../../common/constants'; const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) => - `/${hostName}/${tabName}`; + `${HOSTS_PATH}/${hostName}/${tabName}`; export const navTabsHostDetails = ( hostName: string, @@ -24,44 +24,30 @@ export const navTabsHostDetails = ( name: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.authentications), disabled: false, - urlKey: 'host', - isDetailPage: true, - pageId: SecurityPageName.hosts, }, [HostsTableType.uncommonProcesses]: { id: HostsTableType.uncommonProcesses, name: i18n.NAVIGATION_UNCOMMON_PROCESSES_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.uncommonProcesses), disabled: false, - urlKey: 'host', - isDetailPage: true, - pageId: SecurityPageName.hosts, }, [HostsTableType.anomalies]: { id: HostsTableType.anomalies, name: i18n.NAVIGATION_ANOMALIES_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.anomalies), disabled: false, - urlKey: 'host', - isDetailPage: true, - pageId: SecurityPageName.hosts, }, [HostsTableType.events]: { id: HostsTableType.events, name: i18n.NAVIGATION_EVENTS_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.events), disabled: false, - urlKey: 'host', - isDetailPage: true, - pageId: SecurityPageName.hosts, }, [HostsTableType.alerts]: { id: HostsTableType.alerts, name: i18n.NAVIGATION_ALERTS_TITLE, href: getTabsOnHostDetailsUrl(hostName, HostsTableType.alerts), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts index 5e3541feeb0aa..f4e14605cab47 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts @@ -37,8 +37,9 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.hosts}`, { + href: getUrlForApp(APP_ID, { path: !isEmpty(search[0]) ? search[0] : '', + deepLinkId: SecurityPageName.hosts, }), }, ]; @@ -48,12 +49,14 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.detailName, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.hosts}`, { + href: getUrlForApp(APP_ID, { path: getHostDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), + deepLinkId: SecurityPageName.hosts, }), }, ]; } + if (params.tabName != null) { const tabName = get('tabName', params); if (!tabName) return breadcrumb; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 67a2b9419f9b8..876730a8f66c4 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -15,7 +15,7 @@ import { HostsTableType } from '../store/model'; import { AnomaliesQueryTabBody } from '../../common/containers/anomalies/anomalies_query_tab_body'; import { AnomaliesHostTable } from '../../common/components/ml/tables/anomalies_host_table'; import { UpdateDateRange } from '../../common/components/charts/common'; - +import { HOSTS_PATH } from '../../../common/constants'; import { HostsQueryTabBody, AuthenticationsQueryTabBody, @@ -79,22 +79,22 @@ export const HostsTabs = memo<HostsTabsProps>( return ( <Switch> - <Route path={`/:tabName(${HostsTableType.hosts})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.hosts})`}> <HostsQueryTabBody docValueFields={docValueFields} {...tabProps} /> </Route> - <Route path={`/:tabName(${HostsTableType.authentications})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.authentications})`}> <AuthenticationsQueryTabBody docValueFields={docValueFields} {...tabProps} /> </Route> - <Route path={`/:tabName(${HostsTableType.uncommonProcesses})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.uncommonProcesses})`}> <UncommonProcessQueryTabBody {...tabProps} /> </Route> - <Route path={`/:tabName(${HostsTableType.anomalies})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.anomalies})`}> <AnomaliesQueryTabBody {...tabProps} AnomaliesTableComponent={AnomaliesHostTable} /> </Route> - <Route path={`/:tabName(${HostsTableType.events})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.events})`}> <EventsQueryTabBody {...tabProps} /> </Route> - <Route path={`/:tabName(${HostsTableType.alerts})`}> + <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.alerts})`}> <HostAlertsQueryTabBody {...tabProps} /> </Route> </Switch> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx index e4f447528ead6..23be8f09ce140 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/index.tsx @@ -6,17 +6,17 @@ */ import React from 'react'; -import { Route, Switch, RouteComponentProps, useHistory } from 'react-router-dom'; - +import { Route, Switch, Redirect } from 'react-router-dom'; +import { HOSTS_PATH } from '../../../common/constants'; import { HostDetails } from './details'; import { HostsTableType } from '../store/model'; import { MlHostConditionalContainer } from '../../common/components/ml/conditional_links/ml_host_conditional_container'; import { Hosts } from './hosts'; -import { hostsPagePath, hostDetailsPagePath } from './types'; +import { hostDetailsPagePath } from './types'; const getHostsTabPath = () => - `/:tabName(` + + `${HOSTS_PATH}/:tabName(` + `${HostsTableType.hosts}|` + `${HostsTableType.authentications}|` + `${HostsTableType.uncommonProcesses}|` + @@ -24,7 +24,7 @@ const getHostsTabPath = () => `${HostsTableType.events}|` + `${HostsTableType.alerts})`; -const getHostDetailsTabPath = (pagePath: string) => +const getHostDetailsTabPath = () => `${hostDetailsPagePath}/:tabName(` + `${HostsTableType.authentications}|` + `${HostsTableType.uncommonProcesses}|` + @@ -32,24 +32,26 @@ const getHostDetailsTabPath = (pagePath: string) => `${HostsTableType.events}|` + `${HostsTableType.alerts})`; -type Props = Partial<RouteComponentProps<{}>> & { url: string }; - -export const HostsContainer = React.memo<Props>(({ url }) => { - const history = useHistory(); - +export const HostsContainer = React.memo(() => { return ( <Switch> <Route - path="/ml-hosts" - render={({ location, match }) => ( - <MlHostConditionalContainer location={location} url={match.url} /> + exact + strict + path={HOSTS_PATH} + render={({ location: { search = '' } }) => ( + <Redirect to={{ pathname: `${HOSTS_PATH}/${HostsTableType.hosts}`, search }} /> )} /> + + <Route path={`${HOSTS_PATH}/ml-hosts`}> + <MlHostConditionalContainer /> + </Route> <Route path={getHostsTabPath()}> <Hosts /> </Route> <Route - path={getHostDetailsTabPath(hostsPagePath)} + path={getHostDetailsTabPath()} render={({ match: { params: { detailName }, @@ -63,20 +65,14 @@ export const HostsContainer = React.memo<Props>(({ url }) => { params: { detailName }, }, location: { search = '' }, - }) => { - history.replace(`${detailName}/${HostsTableType.authentications}${search}`); - return null; - }} - /> - - <Route - exact - strict - path="" - render={({ location: { search = '' } }) => { - history.replace(`${HostsTableType.hosts}${search}`); - return null; - }} + }) => ( + <Redirect + to={{ + pathname: `${HOSTS_PATH}/${detailName}/${HostsTableType.authentications}`, + search, + }} + /> + )} /> </Switch> ); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx index 41f0dda8ea0cc..c43a6431d2da8 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/nav_tabs.tsx @@ -9,9 +9,9 @@ import { omit } from 'lodash/fp'; import * as i18n from './translations'; import { HostsTableType } from '../store/model'; import { HostsNavTab } from './navigation/types'; -import { SecurityPageName } from '../../app/types'; +import { HOSTS_PATH } from '../../../common/constants'; -const getTabsOnHostsUrl = (tabName: HostsTableType) => `/${tabName}`; +const getTabsOnHostsUrl = (tabName: HostsTableType) => `${HOSTS_PATH}/${tabName}`; export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => { const hostsNavTabs = { @@ -20,48 +20,36 @@ export const navTabsHosts = (hasMlUserPermissions: boolean): HostsNavTab => { name: i18n.NAVIGATION_ALL_HOSTS_TITLE, href: getTabsOnHostsUrl(HostsTableType.hosts), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, [HostsTableType.authentications]: { id: HostsTableType.authentications, name: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, href: getTabsOnHostsUrl(HostsTableType.authentications), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, [HostsTableType.uncommonProcesses]: { id: HostsTableType.uncommonProcesses, name: i18n.NAVIGATION_UNCOMMON_PROCESSES_TITLE, href: getTabsOnHostsUrl(HostsTableType.uncommonProcesses), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, [HostsTableType.anomalies]: { id: HostsTableType.anomalies, name: i18n.NAVIGATION_ANOMALIES_TITLE, href: getTabsOnHostsUrl(HostsTableType.anomalies), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, [HostsTableType.events]: { id: HostsTableType.events, name: i18n.NAVIGATION_EVENTS_TITLE, href: getTabsOnHostsUrl(HostsTableType.events), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, [HostsTableType.alerts]: { id: HostsTableType.alerts, name: i18n.NAVIGATION_ALERTS_TITLE, href: getTabsOnHostsUrl(HostsTableType.alerts), disabled: false, - urlKey: 'host', - pageId: SecurityPageName.hosts, }, }; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/types.ts index f810a73d12596..c7fd743ee5e44 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/types.ts @@ -11,9 +11,9 @@ import { hostsModel } from '../store'; import { GlobalTimeArgs } from '../../common/containers/use_global_time'; import { InputsModelId } from '../../common/store/inputs/constants'; import { DocValueFields } from '../../common/containers/source'; +import { HOSTS_PATH } from '../../../common/constants'; -export const hostsPagePath = '/'; -export const hostDetailsPagePath = `/:detailName`; +export const hostDetailsPagePath = `${HOSTS_PATH}/:detailName`; export type HostsTabsProps = GlobalTimeArgs & { docValueFields: DocValueFields[]; diff --git a/x-pack/plugins/security_solution/public/hosts/routes.tsx b/x-pack/plugins/security_solution/public/hosts/routes.tsx index e15acdebeeb40..1eeddf2155613 100644 --- a/x-pack/plugins/security_solution/public/hosts/routes.tsx +++ b/x-pack/plugins/security_solution/public/hosts/routes.tsx @@ -6,14 +6,20 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - import { HostsContainer } from './pages'; -import { NotFoundPage } from '../app/404'; +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { HOSTS_PATH } from '../../common/constants'; export const HostsRoutes = () => ( - <Switch> - <Route path="/" render={({ match }) => <HostsContainer url={match.url} />} /> - <Route render={() => <NotFoundPage />} /> - </Switch> + <TrackApplicationView viewId={SecurityPageName.hosts}> + <HostsContainer /> + </TrackApplicationView> ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: HOSTS_PATH, + render: HostsRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx b/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx index 536d1d084f0c5..daf2c55a44333 100644 --- a/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx +++ b/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx @@ -13,5 +13,4 @@ import { renderApp } from './app'; import { createStore, createInitialState } from './common/store'; - export { renderApp, createStore, createInitialState }; diff --git a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx index c36900a7ce8d5..47026cbec49ad 100644 --- a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -10,11 +10,15 @@ * By loading these later we can reduce the initial bundle size and allow users to delay loading these dependencies until they are needed. */ -import { Detections } from './detections'; import { Cases } from './cases'; +import { Detections } from './detections'; +import { Exceptions } from './exceptions'; + import { Hosts } from './hosts'; import { Network } from './network'; import { Overview } from './overview'; +import { Rules } from './rules'; + import { Timelines } from './timelines'; import { Management } from './management'; @@ -24,9 +28,11 @@ import { Management } from './management'; const subPluginClasses = { Detections, Cases, + Exceptions, Hosts, Network, Overview, + Rules, Timelines, Management, }; diff --git a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts index 3bcbd81621588..d437c45792766 100644 --- a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts @@ -29,7 +29,8 @@ export function getBreadcrumbs( return [ { text: ADMINISTRATION, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.administration}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.endpoints, path: !isEmpty(search[0]) ? search[0] : '', }), }, diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index 2ed00309992a8..f6b147d729322 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -5,18 +5,15 @@ * 2.0. */ +import { MANAGEMENT_PATH } from '../../../common/constants'; import { ManagementStoreGlobalNamespace, AdministrationSubTab } from '../types'; -import { APP_ID } from '../../../common/constants'; -import { SecurityPageName } from '../../app/types'; // --[ ROUTING ]--------------------------------------------------------------------------- -export const MANAGEMENT_APP_ID = `${APP_ID}:${SecurityPageName.administration}`; -export const MANAGEMENT_ROUTING_ROOT_PATH = ''; -export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.endpoints})`; -export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})`; -export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`; -export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`; -export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`; +export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.endpoints})`; +export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})`; +export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`; +export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`; +export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`; // --[ STORE ]--------------------------------------------------------------------------- /** The SIEM global store namespace where the management state will be mounted */ diff --git a/x-pack/plugins/security_solution/public/management/common/routing.test.ts b/x-pack/plugins/security_solution/public/management/common/routing.test.ts index a1662c21012be..82b7a15d642e4 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.test.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.test.ts @@ -98,27 +98,35 @@ describe('routing', () => { describe('getTrustedAppsListPath()', () => { it('builds proper path when no parameters provided', () => { - expect(getTrustedAppsListPath()).toEqual('/trusted_apps'); + expect(getTrustedAppsListPath()).toEqual('/administration/trusted_apps'); }); it('builds proper path when empty parameters provided', () => { - expect(getTrustedAppsListPath({})).toEqual('/trusted_apps'); + expect(getTrustedAppsListPath({})).toEqual('/administration/trusted_apps'); }); it('builds proper path when only page size provided', () => { - expect(getTrustedAppsListPath({ page_size: 20 })).toEqual('/trusted_apps?page_size=20'); + expect(getTrustedAppsListPath({ page_size: 20 })).toEqual( + '/administration/trusted_apps?page_size=20' + ); }); it('builds proper path when only page index provided', () => { - expect(getTrustedAppsListPath({ page_index: 2 })).toEqual('/trusted_apps?page_index=2'); + expect(getTrustedAppsListPath({ page_index: 2 })).toEqual( + '/administration/trusted_apps?page_index=2' + ); }); it('builds proper path when only "show" provided', () => { - expect(getTrustedAppsListPath({ show: 'create' })).toEqual('/trusted_apps?show=create'); + expect(getTrustedAppsListPath({ show: 'create' })).toEqual( + '/administration/trusted_apps?show=create' + ); }); it('builds proper path when only view type provided', () => { - expect(getTrustedAppsListPath({ view_type: 'list' })).toEqual('/trusted_apps?view_type=list'); + expect(getTrustedAppsListPath({ view_type: 'list' })).toEqual( + '/administration/trusted_apps?view_type=list' + ); }); it('builds proper path when all params provided', () => { @@ -131,7 +139,7 @@ describe('routing', () => { }; expect(getTrustedAppsListPath(location)).toEqual( - '/trusted_apps?page_index=2&page_size=20&view_type=list&show=create' + '/administration/trusted_apps?page_index=2&page_size=20&view_type=list&show=create' ); }); @@ -143,7 +151,7 @@ describe('routing', () => { view_type: 'list', }); - expect(path).toEqual('/trusted_apps?page_size=20&view_type=list&show=create'); + expect(path).toEqual('/administration/trusted_apps?page_size=20&view_type=list&show=create'); }); it('builds proper path when page size is equal to default', () => { @@ -154,7 +162,7 @@ describe('routing', () => { view_type: 'list', }); - expect(path).toEqual('/trusted_apps?page_index=2&view_type=list&show=create'); + expect(path).toEqual('/administration/trusted_apps?page_index=2&view_type=list&show=create'); }); it('builds proper path when "show" is equal to default', () => { @@ -165,7 +173,7 @@ describe('routing', () => { view_type: 'list', }); - expect(path).toEqual('/trusted_apps?page_index=2&page_size=20&view_type=list'); + expect(path).toEqual('/administration/trusted_apps?page_index=2&page_size=20&view_type=list'); }); it('builds proper path when view type is equal to default', () => { @@ -176,7 +184,7 @@ describe('routing', () => { view_type: 'grid', }); - expect(path).toEqual('/trusted_apps?page_index=2&page_size=20&show=create'); + expect(path).toEqual('/administration/trusted_apps?page_index=2&page_size=20&show=create'); }); it('builds proper path when params are equal to default', () => { @@ -187,7 +195,7 @@ describe('routing', () => { view_type: 'grid', }); - expect(path).toEqual('/trusted_apps'); + expect(path).toEqual('/administration/trusted_apps'); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index 021c900824f8d..09b47b76c486f 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -6,25 +6,13 @@ */ import React, { FC, memo } from 'react'; -import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui'; +import { EuiPanel, CommonProps } from '@elastic/eui'; import styled from 'styled-components'; import { SecurityPageName } from '../../../common/constants'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { HeaderPage } from '../../common/components/header_page'; -import { SecuritySolutionTabNavigation } from '../../common/components/navigation'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { AdministrationSubTab } from '../types'; -import { - ENDPOINTS_TAB, - TRUSTED_APPS_TAB, - BETA_BADGE_LABEL, - EVENT_FILTERS_TAB, -} from '../common/translations'; -import { - getEndpointListPath, - getEventFiltersListPath, - getTrustedAppsListPath, -} from '../common/routing'; +import { BETA_BADGE_LABEL } from '../common/translations'; /** Ensure that all flyouts z-index in Administation area show the flyout header */ const EuiPanelStyled = styled(EuiPanel)` @@ -57,37 +45,6 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp {actions} </HeaderPage> - <SecuritySolutionTabNavigation - navTabs={{ - [AdministrationSubTab.endpoints]: { - name: ENDPOINTS_TAB, - id: AdministrationSubTab.endpoints, - href: getEndpointListPath({ name: 'endpointList' }), - urlKey: 'administration', - pageId: SecurityPageName.administration, - disabled: false, - }, - [AdministrationSubTab.trustedApps]: { - name: TRUSTED_APPS_TAB, - id: AdministrationSubTab.trustedApps, - href: getTrustedAppsListPath(), - urlKey: 'administration', - pageId: SecurityPageName.administration, - disabled: false, - }, - [AdministrationSubTab.eventFilters]: { - name: EVENT_FILTERS_TAB, - id: AdministrationSubTab.eventFilters, - href: getEventFiltersListPath(), - urlKey: 'administration', - pageId: SecurityPageName.administration, - disabled: false, - }, - }} - /> - - <EuiSpacer /> - <EuiPanelStyled hasBorder>{children}</EuiPanelStyled> <SpyRoute pageName={SecurityPageName.administration} /> diff --git a/x-pack/plugins/security_solution/public/management/components/hooks/use_management_format_url.ts b/x-pack/plugins/security_solution/public/management/components/hooks/use_management_format_url.ts deleted file mode 100644 index a90794a075b45..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/hooks/use_management_format_url.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useKibana } from '../../../common/lib/kibana'; -import { MANAGEMENT_APP_ID } from '../../common/constants'; - -/** - * Returns a full URL to the provided Management page path by using - * kibana's `getUrlForApp()` - * - * @param managementPath - */ -export const useManagementFormatUrl = (managementPath: string) => { - return `${useKibana().services.application.getUrlForApp(MANAGEMENT_APP_ID)}${managementPath}`; -}; diff --git a/x-pack/plugins/security_solution/public/management/index.ts b/x-pack/plugins/security_solution/public/management/index.ts index 2859adae97d41..326f8471aa621 100644 --- a/x-pack/plugins/security_solution/public/management/index.ts +++ b/x-pack/plugins/security_solution/public/management/index.ts @@ -7,7 +7,7 @@ import { CoreStart } from 'kibana/public'; import { Reducer, CombinedState } from 'redux'; -import { ManagementRoutes } from './routes'; +import { routes } from './routes'; import { StartPlugins } from '../types'; import { SecuritySubPluginWithStore } from '../app/types'; import { managementReducer } from './store/reducer'; @@ -39,7 +39,7 @@ export class Management { plugins: StartPlugins ): SecuritySubPluginWithStore<'management', ManagementState> { return { - SubPluginRoutes: ManagementRoutes, + routes, store: { initialState: { management: undefined, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx index a860e3c45deee..0b5ff7cc4da0f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx @@ -41,7 +41,7 @@ describe('When using the EndpointAgentStatus component', () => { }; act(() => { - mockedContext.history.push('/endpoints'); + mockedContext.history.push('/administration/endpoints'); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx index c152da1029395..2919fdd15e29d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_policy_link.tsx @@ -10,9 +10,8 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import { useEndpointSelector } from '../hooks'; import { nonExistingPolicies } from '../../store/selectors'; import { getPolicyDetailPath } from '../../../../common/routing'; -import { useFormatUrl } from '../../../../../common/components/link_to'; -import { SecurityPageName } from '../../../../../../common/constants'; import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; +import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; /** * A policy link (to details) that first checks to see if the policy id exists against @@ -25,14 +24,14 @@ export const EndpointPolicyLink = memo< } >(({ policyId, children, onClick, ...otherProps }) => { const missingPolicies = useEndpointSelector(nonExistingPolicies); - const { formatUrl } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const { toRoutePath, toRouteUrl } = useMemo(() => { - const toPath = getPolicyDetailPath(policyId); + const path = getPolicyDetailPath(policyId); return { - toRoutePath: toPath, - toRouteUrl: formatUrl(toPath), + toRoutePath: path, + toRouteUrl: getAppUrl({ path }), }; - }, [formatUrl, policyId]); + }, [policyId, getAppUrl]); const clickHandler = useNavigateByRouterEventHandler(toRoutePath, onClick); if (missingPolicies[policyId]) { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 04708ea90cd34..04fd8cd715c87 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -17,7 +17,21 @@ import { endpointPageHttpMock } from '../../../mocks'; import { fireEvent } from '@testing-library/dom'; import { licenseService } from '../../../../../../common/hooks/use_license'; -jest.mock('../../../../../../common/lib/kibana'); +jest.mock('../../../../../../common/lib/kibana/kibana_react', () => { + const originalModule = jest.requireActual('../../../../../../common/lib/kibana/kibana_react'); + return { + ...originalModule, + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + getUrlForApp: (appId: string, options?: { path?: string }) => + `/app/${appId}${options?.path}`, + navigateToApp: jest.fn(), + }, + }, + }), + }; +}); jest.mock('../../../../../../common/hooks/use_license'); describe('When using the Endpoint Details Actions Menu', () => { @@ -45,7 +59,7 @@ describe('When using the Endpoint Details Actions Menu', () => { act(() => { mockedContext.history.push( - '/endpoints?selected_endpoint=5fe11314-678c-413e-87a2-b4a3461878ee' + '/administration/endpoints?selected_endpoint=5fe11314-678c-413e-87a2-b4a3461878ee' ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/back_to_endpoint_details_flyout_subheader.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/back_to_endpoint_details_flyout_subheader.tsx index 7218e794f587a..6c9ae7631e6d3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/back_to_endpoint_details_flyout_subheader.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/back_to_endpoint_details_flyout_subheader.tsx @@ -10,14 +10,13 @@ import { i18n } from '@kbn/i18n'; import { FlyoutSubHeader, FlyoutSubHeaderProps } from './flyout_sub_header'; import { getEndpointDetailsPath } from '../../../../../common/routing'; import { useNavigateByRouterEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; -import { useFormatUrl } from '../../../../../../common/components/link_to'; -import { SecurityPageName } from '../../../../../../../common/constants'; import { useEndpointSelector } from '../../hooks'; import { uiQueryParams } from '../../../store/selectors'; +import { useAppUrl } from '../../../../../../common/lib/kibana/hooks'; export const BackToEndpointDetailsFlyoutSubHeader = memo<{ endpointId: string }>( ({ endpointId }) => { - const { formatUrl } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const { show, ...currentUrlQueryParams } = useEndpointSelector(uiQueryParams); const detailsRoutePath = useMemo( @@ -37,10 +36,10 @@ export const BackToEndpointDetailsFlyoutSubHeader = memo<{ endpointId: string }> title: i18n.translate('xpack.securitySolution.endpoint.policyResponse.backLinkTitle', { defaultMessage: 'Endpoint Details', }), - href: formatUrl(detailsRoutePath), + href: getAppUrl({ path: detailsRoutePath }), onClick: backToDetailsClickHandler, }; - }, [backToDetailsClickHandler, detailsRoutePath, formatUrl]); + }, [backToDetailsClickHandler, getAppUrl, detailsRoutePath]); return ( <FlyoutSubHeader diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx index 64ea575c37d79..369b4c128e052 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx @@ -27,11 +27,10 @@ import { POLICY_STATUS_TO_BADGE_COLOR } from '../host_constants'; import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time'; import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { getEndpointDetailsPath } from '../../../../common/routing'; -import { SecurityPageName } from '../../../../../app/types'; -import { useFormatUrl } from '../../../../../common/components/link_to'; import { EndpointPolicyLink } from '../components/endpoint_policy_link'; import { OutOfDate } from '../components/out_of_date'; import { EndpointAgentStatus } from '../components/endpoint_agent_status'; +import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -54,26 +53,18 @@ export const EndpointDetails = memo( const policyStatus = useEndpointSelector( policyResponseStatus ) as keyof typeof POLICY_STATUS_TO_BADGE_COLOR; - const { formatUrl } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const [policyResponseUri, policyResponseRoutePath] = useMemo(() => { // eslint-disable-next-line @typescript-eslint/naming-convention const { selected_endpoint, show, ...currentUrlParams } = queryParams; - return [ - formatUrl( - getEndpointDetailsPath({ - name: 'endpointPolicyResponse', - ...currentUrlParams, - selected_endpoint: details.agent.id, - }) - ), - getEndpointDetailsPath({ - name: 'endpointPolicyResponse', - ...currentUrlParams, - selected_endpoint: details.agent.id, - }), - ]; - }, [details.agent.id, formatUrl, queryParams]); + const path = getEndpointDetailsPath({ + name: 'endpointPolicyResponse', + ...currentUrlParams, + selected_endpoint: details.agent.id, + }); + return [getAppUrl({ path }), path]; + }, [details.agent.id, getAppUrl, queryParams]); const policyStatusClickHandler = useNavigateByRouterEventHandler(policyResponseRoutePath); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts index 4c00c00e50dbc..ca14dde18455b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts @@ -13,7 +13,7 @@ import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_GLOBAL_NAMESPACE, } from '../../../../common/constants'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useAppUrl } from '../../../../../common/lib/kibana'; export function useEndpointSelector<TSelected>(selector: (state: EndpointState) => TSelected) { return useSelector(function (state: State) { @@ -29,15 +29,15 @@ export function useEndpointSelector<TSelected>(selector: (state: EndpointState) * Returns an object that contains Fleet app and URL information */ export const useIngestUrl = (subpath: string): { url: string; appId: string; appPath: string } => { - const { services } = useKibana(); + const { getAppUrl } = useAppUrl(); return useMemo(() => { const appPath = `#/${subpath}`; return { - url: `${services.application.getUrlForApp('fleet')}${appPath}`, + url: `${getAppUrl({ appId: 'fleet' })}${appPath}`, appId: 'fleet', appPath, }; - }, [services.application, subpath]); + }, [getAppUrl, subpath]); }; /** * Returns an object that contains Fleet app and URL information @@ -45,13 +45,13 @@ export const useIngestUrl = (subpath: string): { url: string; appId: string; app export const useAgentDetailsIngestUrl = ( agentId: string ): { url: string; appId: string; appPath: string } => { - const { services } = useKibana(); + const { getAppUrl } = useAppUrl(); return useMemo(() => { const appPath = `#/fleet/agents/${agentId}/activity`; return { - url: `${services.application.getUrlForApp('fleet')}${appPath}`, + url: `${getAppUrl({ appId: 'fleet' })}${appPath}`, appId: 'fleet', appPath, }; - }, [services.application, agentId]); + }, [getAppUrl, agentId]); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 408e1794ef680..0422cbcaa4310 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -7,15 +7,13 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { MANAGEMENT_APP_ID } from '../../../../common/constants'; -import { APP_ID, SecurityPageName } from '../../../../../../common/constants'; +import { APP_ID } from '../../../../../../common/constants'; import { pagePathGetters } from '../../../../../../../fleet/public'; import { getEndpointDetailsPath } from '../../../../common/routing'; import { HostMetadata, MaybeImmutable } from '../../../../../../common/endpoint/types'; -import { useFormatUrl } from '../../../../../common/components/link_to'; import { useEndpointSelector } from './hooks'; import { agentPolicies, uiQueryParams } from '../../store/selectors'; -import { useKibana } from '../../../../../common/lib/kibana'; +import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer'; import { isEndpointHostIsolated } from '../../../../../common/utils/validators'; import { useLicense } from '../../../../../common/hooks/use_license'; @@ -28,14 +26,9 @@ export const useEndpointActionItems = ( endpointMetadata: MaybeImmutable<HostMetadata> | undefined ): ContextMenuItemNavByRouterProps[] => { const isPlatinumPlus = useLicense().isPlatinumPlus(); - const { formatUrl } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const fleetAgentPolicies = useEndpointSelector(agentPolicies); const allCurrentUrlParams = useEndpointSelector(uiQueryParams); - const { - services: { - application: { getUrlForApp }, - }, - } = useKibana(); return useMemo<ContextMenuItemNavByRouterProps[]>(() => { if (endpointMetadata) { @@ -68,11 +61,11 @@ export const useEndpointActionItems = ( 'data-test-subj': 'unIsolateLink', icon: 'logoSecurity', key: 'unIsolateHost', - navigateAppId: MANAGEMENT_APP_ID, + navigateAppId: APP_ID, navigateOptions: { path: endpointUnIsolatePath, }, - href: formatUrl(endpointUnIsolatePath), + href: getAppUrl({ path: endpointUnIsolatePath }), children: ( <FormattedMessage id="xpack.securitySolution.endpoint.actions.unIsolateHost" @@ -86,11 +79,11 @@ export const useEndpointActionItems = ( 'data-test-subj': 'isolateLink', icon: 'logoSecurity', key: 'isolateHost', - navigateAppId: MANAGEMENT_APP_ID, + navigateAppId: APP_ID, navigateOptions: { path: endpointIsolatePath, }, - href: formatUrl(endpointIsolatePath), + href: getAppUrl({ path: endpointIsolatePath }), children: ( <FormattedMessage id="xpack.securitySolution.endpoint.actions.isolateHost" @@ -107,8 +100,8 @@ export const useEndpointActionItems = ( icon: 'logoSecurity', key: 'hostDetailsLink', navigateAppId: APP_ID, - navigateOptions: { path: `hosts/${endpointHostName}` }, - href: `${getUrlForApp('securitySolution')}/hosts/${endpointHostName}`, + navigateOptions: { path: `/hosts/${endpointHostName}` }, + href: getAppUrl({ path: `/hosts/${endpointHostName}` }), children: ( <FormattedMessage id="xpack.securitySolution.endpoint.actions.hostDetails" @@ -128,7 +121,7 @@ export const useEndpointActionItems = ( })[1] }`, }, - href: `${getUrlForApp('fleet')}#${ + href: `${getAppUrl({ appId: 'fleet' })}#${ pagePathGetters.policy_details({ policyId: fleetAgentPolicies[endpointPolicyId], })[1] @@ -153,7 +146,7 @@ export const useEndpointActionItems = ( })[1] }`, }, - href: `${getUrlForApp('fleet')}#${ + href: `${getAppUrl({ appId: 'fleet' })}#${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] @@ -177,7 +170,7 @@ export const useEndpointActionItems = ( })[1] }/activity?openReassignFlyout=true`, }, - href: `${getUrlForApp('fleet')}#${ + href: `${getAppUrl({ appId: 'fleet' })}#${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] @@ -193,12 +186,5 @@ export const useEndpointActionItems = ( } return []; - }, [ - allCurrentUrlParams, - endpointMetadata, - fleetAgentPolicies, - formatUrl, - getUrlForApp, - isPlatinumPlus, - ]); + }, [allCurrentUrlParams, endpointMetadata, fleetAgentPolicies, getAppUrl, isPlatinumPlus]); }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 4869ce84fad2c..7bfd77e7dd975 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -42,6 +42,7 @@ import { import { getCurrentIsolationRequestState } from '../store/selectors'; import { licenseService } from '../../../../common/hooks/use_license'; import { FleetActionGenerator } from '../../../../../common/endpoint/data_generators/fleet_action_generator'; +import { APP_PATH, MANAGEMENT_PATH } from '../../../../../common/constants'; // not sure why this can't be imported from '../../../../common/mock/formatted_relative'; // but sure enough it needs to be inline in this one file @@ -94,7 +95,7 @@ describe('when on the endpoint list page', () => { ({ history, store, coreStart, middlewareSpy } = mockedContext); render = () => mockedContext.render(<EndpointList />); reactTestingLibrary.act(() => { - history.push('/endpoints'); + history.push(`${MANAGEMENT_PATH}/endpoints`); }); // Because `.../common/lib/kibana` was mocked, we need to alter these hooks (which are jest.MockFunctions) @@ -442,7 +443,9 @@ describe('when on the endpoint list page', () => { }); const firstPolicyName = (await renderResult.findAllByTestId('policyNameCellLink'))[0]; expect(firstPolicyName).not.toBeNull(); - expect(firstPolicyName.getAttribute('href')).toContain(`policy/${firstPolicyID}`); + expect(firstPolicyName.getAttribute('href')).toEqual( + `${APP_PATH}${MANAGEMENT_PATH}/policy/${firstPolicyID}` + ); }); describe('when the user clicks the first hostname in the table', () => { @@ -657,7 +660,7 @@ describe('when on the endpoint list page', () => { mockEndpointListApi(); reactTestingLibrary.act(() => { - history.push('/endpoints?selected_endpoint=1'); + history.push(`${MANAGEMENT_PATH}/endpoints?selected_endpoint=1`); }); renderAndWaitForData = async () => { @@ -682,7 +685,7 @@ describe('when on the endpoint list page', () => { const policyDetailsLink = await renderResult.findByTestId('policyDetailsValue'); expect(policyDetailsLink).not.toBeNull(); expect(policyDetailsLink.getAttribute('href')).toEqual( - `/policy/${hostDetails.metadata.Endpoint.policy.applied.id}` + `${APP_PATH}${MANAGEMENT_PATH}/policy/${hostDetails.metadata.Endpoint.policy.applied.id}` ); }); @@ -704,7 +707,7 @@ describe('when on the endpoint list page', () => { }); const changedUrlAction = await userChangedUrlChecker; expect(changedUrlAction.payload.pathname).toEqual( - `/policy/${hostDetails.metadata.Endpoint.policy.applied.id}` + `${MANAGEMENT_PATH}/policy/${hostDetails.metadata.Endpoint.policy.applied.id}` ); }); @@ -713,7 +716,7 @@ describe('when on the endpoint list page', () => { const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); expect(policyStatusLink).not.toBeNull(); expect(policyStatusLink.getAttribute('href')).toEqual( - '/endpoints?page_index=0&page_size=10&selected_endpoint=1&show=policy_response' + `${APP_PATH}${MANAGEMENT_PATH}/endpoints?page_index=0&page_size=10&selected_endpoint=1&show=policy_response` ); }); @@ -1003,8 +1006,8 @@ describe('when on the endpoint list page', () => { it('should include the back to details link', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); - expect(subHeaderBackLink.getAttribute('href')).toBe( - '/endpoints?page_index=0&page_size=10&selected_endpoint=1' + expect(subHeaderBackLink.getAttribute('href')).toEqual( + `${APP_PATH}${MANAGEMENT_PATH}/endpoints?page_index=0&page_size=10&selected_endpoint=1` ); }); @@ -1055,7 +1058,7 @@ describe('when on the endpoint list page', () => { beforeEach(async () => { getKibanaServicesMock.mockReturnValue(coreStart); reactTestingLibrary.act(() => { - history.push('/endpoints?selected_endpoint=1&show=isolate'); + history.push(`${MANAGEMENT_PATH}/endpoints?selected_endpoint=1&show=isolate`); }); renderResult = await renderAndWaitForData(); // Need to reset `http.post` and adjust it so that the mock for http host @@ -1073,12 +1076,12 @@ describe('when on the endpoint list page', () => { const backButtonLink = renderResult.getByTestId('flyoutSubHeaderBackButton'); expect(backButtonLink.getAttribute('href')).toEqual( - getEndpointDetailsPath({ + `${APP_PATH}${getEndpointDetailsPath({ name: 'endpointDetails', page_index: '0', page_size: '10', selected_endpoint: '1', - }) + })}` ); const changeUrlAction = middlewareSpy.waitForAction('userChangedUrl'); @@ -1088,7 +1091,7 @@ describe('when on the endpoint list page', () => { }); expect((await changeUrlAction).payload).toMatchObject({ - pathname: '/endpoints', + pathname: `${MANAGEMENT_PATH}/endpoints`, search: '?page_index=0&page_size=10&selected_endpoint=1', }); }); @@ -1101,7 +1104,7 @@ describe('when on the endpoint list page', () => { }); expect((await changeUrlAction).payload).toMatchObject({ - pathname: '/endpoints', + pathname: `${MANAGEMENT_PATH}/endpoints`, search: '?page_index=0&page_size=10&selected_endpoint=1', }); }); @@ -1121,7 +1124,7 @@ describe('when on the endpoint list page', () => { }); expect((await changeUrlAction).payload).toMatchObject({ - pathname: '/endpoints', + pathname: `${MANAGEMENT_PATH}/endpoints`, search: '?page_index=0&page_size=10&selected_endpoint=1', }); }); @@ -1149,7 +1152,7 @@ describe('when on the endpoint list page', () => { }); expect((await changeUrlAction).payload).toMatchObject({ - pathname: '/endpoints', + pathname: `${MANAGEMENT_PATH}/endpoints`, search: '?page_index=0&page_size=10', }); @@ -1207,17 +1210,7 @@ describe('when on the endpoint list page', () => { mockEndpointListApi(); reactTestingLibrary.act(() => { - history.push('/endpoints'); - }); - - coreStart.application.getUrlForApp.mockImplementation((appName) => { - switch (appName) { - case 'securitySolution': - return '/app/security'; - case 'fleet': - return '/app/fleet'; - } - return appName; + history.push(`${MANAGEMENT_PATH}/endpoints`); }); renderResult = render(); @@ -1238,19 +1231,19 @@ describe('when on the endpoint list page', () => { it('navigates to the Host Details Isolate flyout', async () => { const isolateLink = await renderResult.findByTestId('isolateLink'); expect(isolateLink.getAttribute('href')).toEqual( - getEndpointDetailsPath({ + `${APP_PATH}${getEndpointDetailsPath({ name: 'endpointIsolate', page_index: '0', page_size: '10', selected_endpoint: hostInfo.metadata.agent.id, - }) + })}` ); }); it('navigates to the Security Solution Host Details page', async () => { const hostLink = await renderResult.findByTestId('hostLink'); expect(hostLink.getAttribute('href')).toEqual( - `/app/security/hosts/${hostInfo.metadata.host.hostname}` + `${APP_PATH}/hosts/${hostInfo.metadata.host.hostname}` ); }); it('navigates to the Ingest Agent Policy page', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 9316d2539d133..7a553cfa8a32a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -47,6 +47,7 @@ import { import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath, getEndpointDetailsPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; +import { useAppUrl } from '../../../../common/lib/kibana/hooks'; import { EndpointAction } from '../store/action'; import { EndpointPolicyLink } from './components/endpoint_policy_link'; import { OutOfDate } from './components/out_of_date'; @@ -121,7 +122,8 @@ export const EndpointList = () => { endpointsTotalError, isTransformEnabled, } = useEndpointSelector(selector); - const { formatUrl, search } = useFormatUrl(SecurityPageName.administration); + const { search } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const dispatch = useDispatch<(a: EndpointAction) => void>(); // cap ability to page at 10k records. (max_result_window) const maxPageCount = totalItemCount > MAX_PAGINATED_ITEM ? MAX_PAGINATED_ITEM : totalItemCount; @@ -160,13 +162,17 @@ export const EndpointList = () => { }/add-integration`, state: { onCancelNavigateTo: [ - 'securitySolution:administration', - { path: getEndpointListPath({ name: 'endpointList' }) }, + 'securitySolution', + { + path: getEndpointListPath({ name: 'endpointList' }), + }, ], - onCancelUrl: formatUrl(getEndpointListPath({ name: 'endpointList' })), + onCancelUrl: getAppUrl({ path: getEndpointListPath({ name: 'endpointList' }) }), onSaveNavigateTo: [ - 'securitySolution:administration', - { path: getEndpointListPath({ name: 'endpointList' }) }, + 'securitySolution', + { + path: getEndpointListPath({ name: 'endpointList' }), + }, ], }, } @@ -201,7 +207,7 @@ export const EndpointList = () => { path: `#/policies/${selectedPolicyId}?openEnrollmentFlyout=true`, state: { onDoneNavigateTo: [ - 'securitySolution:administration', + 'securitySolution', { path: getEndpointListPath({ name: 'endpointList' }) }, ], }, @@ -257,7 +263,7 @@ export const EndpointList = () => { }, search ); - const toRouteUrl = formatUrl(toRoutePath); + const toRouteUrl = getAppUrl({ path: toRoutePath }); return ( <EuiToolTip content={hostname} anchorClassName="eui-textTruncate"> <EndpointListNavLink @@ -336,7 +342,7 @@ export const EndpointList = () => { ...queryParams, selected_endpoint: item.metadata.agent.id, }); - const toRouteUrl = formatUrl(toRoutePath); + const toRouteUrl = getAppUrl({ path: toRoutePath }); return ( <EuiBadge color={POLICY_STATUS_TO_BADGE_COLOR[policy.status]} @@ -416,7 +422,7 @@ export const EndpointList = () => { ], }, ]; - }, [queryParams, search, formatUrl, PAD_LEFT]); + }, [queryParams, search, getAppUrl, PAD_LEFT]); const renderTableOrEmptyState = useMemo(() => { if (endpointsExist || areEndpointsEnrolling) { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts index 530a18cd8a312..c7da244c49634 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts @@ -81,7 +81,7 @@ describe('Event filters middleware', () => { store.dispatch({ type: 'userChangedUrl', payload: { - pathname: '/event_filters', + pathname: '/administration/event_filters', search: searchParams, hash: '', key: 'ylsd7h', diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts index 2bfc6b4378839..1b856f54d16a9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts @@ -128,7 +128,7 @@ describe('event filters reducer', () => { describe('UserChangedUrl', () => { const userChangedUrlAction = ( search: string = '', - pathname = '/event_filters' + pathname = '/administration/event_filters' ): UserChangedUrl => ({ type: 'userChangedUrl', payload: { search, pathname, hash: '' }, diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx index c594aaa5c7e19..ed18c084c2a05 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx @@ -47,7 +47,7 @@ describe('When event filters delete modal is shown', () => { renderResult = mockedContext.render(<EventFilterDeleteModal />); await act(async () => { - history.push('/event_filters'); + history.push('/administration/event_filters'); await waitForAction('userChangedUrl'); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx index 59d409874c561..d44ce7a136fdf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx @@ -41,7 +41,7 @@ describe('When on the Event Filters List Page', () => { waitForAction = mockedContext.middlewareSpy.waitForAction; act(() => { - history.push('/event_filters'); + history.push('/administration/event_filters'); }); }); @@ -156,7 +156,7 @@ describe('When on the Event Filters List Page', () => { describe('And search is dispatched', () => { beforeEach(async () => { act(() => { - history.push('/event_filters?filter=test'); + history.push('/administration/event_filters?filter=test'); }); renderResult = render(); await act(async () => { @@ -180,7 +180,7 @@ describe('When on the Event Filters List Page', () => { beforeEach(async () => { renderResult = render(); act(() => { - history.push('/event_filters', { + history.push('/administration/event_filters', { onBackButtonNavigateTo: [{ appId: 'appId' }], backButtonLabel: 'back to fleet', backButtonUrl: '/fleet', @@ -196,7 +196,7 @@ describe('When on the Event Filters List Page', () => { it('back button is not present', () => { act(() => { - history.push('/event_filters'); + history.push('/administration/event_filters'); }); expect(renderResult.queryByTestId('backToOrigin')).toBeNull(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx index 68c25e55dc1c9..9f2ed3618b06d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx @@ -16,21 +16,11 @@ jest.mock('../../common/hooks/endpoint/ingest_enabled'); describe('when in the Admistration tab', () => { let render: () => ReturnType<AppContextTestRender['render']>; - let coreStart: AppContextTestRender['coreStart']; beforeEach(() => { const mockedContext = createAppRootMockRenderer(); - coreStart = mockedContext.coreStart; render = () => mockedContext.render(<ManagementContainer />); - coreStart.http.get.mockImplementation(() => - Promise.resolve({ - response: [ - { - name: 'endpoint', - }, - ], - }) - ); + mockedContext.history.push('/administration/endpoints'); }); it('should display the No Permissions view when Ingest is OFF', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 8273f1a6e55c2..b3bee78161f39 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -6,25 +6,24 @@ */ import React, { memo } from 'react'; -import { Route, Switch, useHistory } from 'react-router-dom'; +import { Route, Switch, Redirect } from 'react-router-dom'; import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { MANAGEMENT_ROUTING_ENDPOINTS_PATH, MANAGEMENT_ROUTING_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, - MANAGEMENT_ROUTING_ROOT_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, } from '../common/constants'; import { NotFoundPage } from '../../app/404'; import { EndpointsContainer } from './endpoint_hosts'; import { PolicyContainer } from './policy'; import { TrustedAppsContainer } from './trusted_apps'; -import { getEndpointListPath } from '../common/routing'; -import { SecurityPageName } from '../../../common/constants'; +import { MANAGEMENT_PATH, SecurityPageName } from '../../../common/constants'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; import { EventFiltersContainer } from './event_filters'; +import { getEndpointListPath } from '../common/routing'; const NoPermissions = memo(() => { return ( @@ -56,7 +55,6 @@ const NoPermissions = memo(() => { NoPermissions.displayName = 'NoPermissions'; export const ManagementContainer = memo(() => { - const history = useHistory(); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); if (!isIngestEnabled) { @@ -70,14 +68,9 @@ export const ManagementContainer = memo(() => { <Route path={MANAGEMENT_ROUTING_TRUSTED_APPS_PATH} component={TrustedAppsContainer} /> <Route path={MANAGEMENT_ROUTING_EVENT_FILTERS_PATH} component={EventFiltersContainer} /> - <Route - path={MANAGEMENT_ROUTING_ROOT_PATH} - exact - render={() => { - history.replace(getEndpointListPath({ name: 'endpointList' })); - return null; - }} - /> + <Route path={MANAGEMENT_PATH} exact> + <Redirect to={getEndpointListPath({ name: 'endpointList' })} /> + </Route> <Route path="*" component={NotFoundPage} /> </Switch> ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx index 5588cdbe81e3e..22e1c3a612eb7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx @@ -6,7 +6,6 @@ */ import React, { memo, useMemo, useState, useEffect, useRef } from 'react'; -import { ApplicationStart, CoreStart } from 'kibana/public'; import { EuiPanel, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,27 +13,24 @@ import { PackageCustomExtensionComponentProps, pagePathGetters, } from '../../../../../../../../../fleet/public'; -import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; import { getEventFiltersListPath } from '../../../../../../common/routing'; import { GetExceptionSummaryResponse, ListPageRouteState, } from '../../../../../../../../common/endpoint/types'; import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common'; -import { MANAGEMENT_APP_ID } from '../../../../../../common/constants'; -import { useToasts } from '../../../../../../../common/lib/kibana'; +import { useKibana, useToasts } from '../../../../../../../common/lib/kibana'; +import { useAppUrl } from '../../../../../../../common/lib/kibana/hooks'; import { LinkWithIcon } from './link_with_icon'; import { ExceptionItemsSummary } from './exception_items_summary'; import { EventFiltersHttpService } from '../../../../../event_filters/service'; import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components'; export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => { + const { getAppUrl } = useAppUrl(); const { - services: { - application: { getUrlForApp }, - http, - }, - } = useKibana<CoreStart & { application: ApplicationStart }>(); + services: { http }, + } = useKibana(); const toasts = useToasts(); const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>(); const eventFiltersListUrlPath = getEventFiltersListPath(); @@ -82,11 +78,9 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>( path: fleetPackageCustomUrlPath, }, ], - backButtonUrl: getUrlForApp(INTEGRATIONS_PLUGIN_ID, { - path: fleetPackageCustomUrlPath, - }), + backButtonUrl: getAppUrl({ appId: INTEGRATIONS_PLUGIN_ID, path: fleetPackageCustomUrlPath }), }; - }, [getUrlForApp, pkgkey]); + }, [getAppUrl, pkgkey]); return ( <EuiPanel paddingSize="l"> @@ -107,8 +101,9 @@ export const FleetEventFiltersCard = memo<PackageCustomExtensionComponentProps>( <StyledEuiFlexGridItem gridarea="link" alignitems="flex-end"> <> <LinkWithIcon - appId={MANAGEMENT_APP_ID} - href={getUrlForApp(MANAGEMENT_APP_ID, { path: eventFiltersListUrlPath })} + href={getAppUrl({ + path: eventFiltersListUrlPath, + })} appPath={eventFiltersListUrlPath} appState={eventFiltersRouteState} data-test-subj="linkToEventFilters" diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx index f1c9cb13a27dc..4f10eceb6781c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx @@ -6,7 +6,6 @@ */ import React, { memo, useMemo, useState, useEffect, useRef } from 'react'; -import { ApplicationStart, CoreStart } from 'kibana/public'; import { EuiPanel, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,27 +13,25 @@ import { PackageCustomExtensionComponentProps, pagePathGetters, } from '../../../../../../../../../fleet/public'; -import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; import { getTrustedAppsListPath } from '../../../../../../common/routing'; import { ListPageRouteState, GetExceptionSummaryResponse, } from '../../../../../../../../common/endpoint/types'; import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common'; -import { MANAGEMENT_APP_ID } from '../../../../../../common/constants'; -import { useToasts } from '../../../../../../../common/lib/kibana'; + +import { useAppUrl } from '../../../../../../../common/lib/kibana/hooks'; +import { useKibana, useToasts } from '../../../../../../../common/lib/kibana'; import { LinkWithIcon } from './link_with_icon'; import { ExceptionItemsSummary } from './exception_items_summary'; import { TrustedAppsHttpService } from '../../../../../trusted_apps/service'; import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components'; export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(({ pkgkey }) => { + const { getAppUrl } = useAppUrl(); const { - services: { - application: { getUrlForApp }, - http, - }, - } = useKibana<CoreStart & { application: ApplicationStart }>(); + services: { http }, + } = useKibana(); const toasts = useToasts(); const [stats, setStats] = useState<GetExceptionSummaryResponse | undefined>(); const trustedAppsApi = useMemo(() => new TrustedAppsHttpService(http), [http]); @@ -83,11 +80,9 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(( path: fleetPackageCustomUrlPath, }, ], - backButtonUrl: getUrlForApp(INTEGRATIONS_PLUGIN_ID, { - path: fleetPackageCustomUrlPath, - }), + backButtonUrl: getAppUrl({ appId: INTEGRATIONS_PLUGIN_ID, path: fleetPackageCustomUrlPath }), }; - }, [getUrlForApp, pkgkey]); + }, [getAppUrl, pkgkey]); return ( <EuiPanel paddingSize="l"> <StyledEuiFlexGridGroup alignItems="baseline" justifyContent="center"> @@ -107,8 +102,9 @@ export const FleetTrustedAppsCard = memo<PackageCustomExtensionComponentProps>(( <StyledEuiFlexGridItem gridarea="link" alignitems="flex-end"> <> <LinkWithIcon - appId={MANAGEMENT_APP_ID} - href={getUrlForApp(MANAGEMENT_APP_ID, { path: trustedAppsListUrlPath })} + href={getAppUrl({ + path: trustedAppsListUrlPath, + })} appPath={trustedAppsListUrlPath} appState={trustedAppRouteState} data-test-subj="linkToTrustedApps" diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 1766048a3985a..93cf0f370a715 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -16,7 +16,6 @@ import { getPolicyDetailPath, getEndpointListPath } from '../../../common/routin import { policyListApiPathHandlers } from '../store/test_mock_utils'; import { licenseService } from '../../../../common/hooks/use_license'; -jest.mock('../../../../common/components/link_to'); jest.mock('../../../../common/hooks/use_license'); describe('Policy Details', () => { @@ -130,7 +129,7 @@ describe('Policy Details', () => { const backToListLink = policyView.find('LinkIcon[dataTestSubj="policyDetailsBackLink"]'); expect(backToListLink.prop('iconType')).toBe('arrowLeft'); - expect(backToListLink.prop('href')).toBe(endpointListPath); + expect(backToListLink.prop('href')).toBe(`/app/security${endpointListPath}`); expect(backToListLink.text()).toBe('Back to endpoint hosts'); const pageTitle = policyView.find('h1[data-test-subj="header-page-title"]'); @@ -172,7 +171,7 @@ describe('Policy Details', () => { cancelbutton.simulate('click', { button: 0 }); const navigateToAppMockedCalls = coreStart.application.navigateToApp.mock.calls; expect(navigateToAppMockedCalls[navigateToAppMockedCalls.length - 1]).toEqual([ - 'securitySolution:administration', + 'securitySolution', { path: endpointListPath }, ]); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index e9cdd16554f33..b31ec47fdfc49 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -38,9 +38,8 @@ import { AppAction } from '../../../../common/store/actions'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { SecurityPageName } from '../../../../app/types'; import { getEndpointListPath } from '../../../common/routing'; -import { useFormatUrl } from '../../../../common/components/link_to'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { MANAGEMENT_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../../../common/constants'; import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types'; import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; import { HeaderPage } from '../../../../common/components/header_page'; @@ -73,7 +72,6 @@ export const PolicyDetails = React.memo(() => { }, } = useKibana<{ application: ApplicationStart }>(); const toasts = useToasts(); - const { formatUrl } = useFormatUrl(SecurityPageName.administration); const { state: locationRouteState } = useLocation<PolicyDetailsRouteState>(); // Store values @@ -128,7 +126,7 @@ export const PolicyDetails = React.memo(() => { const routingOnCancelNavigateTo = routeState?.onCancelNavigateTo; const navigateToAppArguments = useMemo((): Parameters<ApplicationStart['navigateToApp']> => { - return routingOnCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }]; + return routingOnCancelNavigateTo ?? [APP_ID, { path: hostListRouterPath }]; }, [hostListRouterPath, routingOnCancelNavigateTo]); const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments); @@ -208,8 +206,7 @@ export const PolicyDetails = React.memo(() => { defaultMessage: 'Back to endpoint hosts', } ), - href: formatUrl(hostListRouterPath), - pageId: SecurityPageName.administration, + pageId: SecurityPageName.endpoints, dataTestSubj: 'policyDetailsBackLink', }} > diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index c17d6df36be68..6374ba3bc4f5f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -46,7 +46,7 @@ export const MalwareProtections = React.memo(() => { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - <LinkToApp appId={`${APP_ID}:${SecurityPageName.detections}`} appPath={`/rules`}> + <LinkToApp appId={APP_ID} deepLinkId={SecurityPageName.rules}> <FormattedMessage id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink" defaultMessage="related detection rules" diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx index 60d20665a6827..70f41015bc257 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx @@ -44,7 +44,7 @@ export const Ransomware = React.memo(() => { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - <LinkToApp appId={`${APP_ID}:${SecurityPageName.detections}`} appPath={`/rules`}> + <LinkToApp appId={APP_ID} deepLinkId={SecurityPageName.rules}> <FormattedMessage id="xpack.securitySolution.endpoint.policy.details.detectionRulesLink" defaultMessage="related detection rules" diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts index ed45d077dd0ca..9624987c8af56 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.test.ts @@ -124,7 +124,9 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); expect(store.getState()).toStrictEqual({ ...initialState, @@ -161,11 +163,15 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); expect(service.getTrustedAppsList).toBeCalledTimes(2); expect(store.getState()).toStrictEqual({ @@ -186,7 +192,7 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(createGetTrustedListAppsResponse(pagination)); - store.dispatch(createUserChangedUrlAction('/trusted_apps')); + store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); @@ -227,7 +233,9 @@ describe('middleware', () => { body: createServerApiError('Internal Server Error'), }); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); @@ -281,7 +289,7 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); service.deleteTrustedApp.mockResolvedValue(); - store.dispatch(createUserChangedUrlAction('/trusted_apps')); + store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); @@ -300,7 +308,7 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); service.deleteTrustedApp.mockResolvedValue(); - store.dispatch(createUserChangedUrlAction('/trusted_apps')); + store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); @@ -340,7 +348,7 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); service.deleteTrustedApp.mockResolvedValue(); - store.dispatch(createUserChangedUrlAction('/trusted_apps')); + store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); @@ -381,7 +389,7 @@ describe('middleware', () => { service.getTrustedAppsList.mockResolvedValue(getTrustedAppsListResponse); service.deleteTrustedApp.mockRejectedValue({ body: notFoundError }); - store.dispatch(createUserChangedUrlAction('/trusted_apps')); + store.dispatch(createUserChangedUrlAction('/administration/trusted_apps')); await spyMiddleware.waitForAction('trustedAppsListResourceStateChanged'); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts index 42659e5cc3498..ac4d29a6016b2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.test.ts @@ -30,7 +30,7 @@ describe('reducer', () => { const result = trustedAppsPageReducer( initialState, createUserChangedUrlAction( - '/trusted_apps', + '/administration/trusted_apps', '?page_index=5&page_size=50&show=create&view_type=list&filter=test' ) ); @@ -55,7 +55,10 @@ describe('reducer', () => { ...initialState, location: { page_index: 5, page_size: 50, view_type: 'grid', filter: '' }, }, - createUserChangedUrlAction('/trusted_apps', '?page_index=b&page_size=60&show=a&view_type=c') + createUserChangedUrlAction( + '/administration/trusted_apps', + '?page_index=b&page_size=60&show=a&view_type=c' + ) ); expect(result).toStrictEqual({ ...initialState, active: true }); @@ -67,7 +70,7 @@ describe('reducer', () => { ...initialState, location: { page_index: 5, page_size: 50, view_type: 'grid', filter: '' }, }, - createUserChangedUrlAction('/trusted_apps') + createUserChangedUrlAction('/administration/trusted_apps') ); expect(result).toStrictEqual({ ...initialState, active: true }); @@ -76,7 +79,7 @@ describe('reducer', () => { it('makes page state inactive and resets list to uninitialised state when navigating away', () => { const result = trustedAppsPageReducer( { ...initialState, listView: createLoadedListViewWithPagination(initialNow), active: true }, - createUserChangedUrlAction('/endpoints') + createUserChangedUrlAction('/administration/endpoints') ); expect(result).toStrictEqual(initialState); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx index 7ec8d311a9156..99db45c0e4b84 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx @@ -21,10 +21,8 @@ import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/sele import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; import { PolicyData } from '../../../../../../../common/endpoint/types'; -import { MANAGEMENT_APP_ID } from '../../../../../common/constants'; import { getPolicyDetailPath } from '../../../../../common/routing'; -import { useFormatUrl } from '../../../../../../common/components/link_to'; -import { SecurityPageName } from '../../../../../../../common/constants'; +import { useAppUrl } from '../../../../../../common/lib/kibana/hooks'; import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; @@ -68,7 +66,7 @@ export const EffectedPolicySelect = memo<EffectedPolicySelectProps>( 'data-test-subj': dataTestSubj, ...otherSelectableProps }) => { - const { formatUrl } = useFormatUrl(SecurityPageName.administration); + const { getAppUrl } = useAppUrl(); const getTestId = useTestIdGenerator(dataTestSubj); @@ -89,8 +87,7 @@ export const EffectedPolicySelect = memo<EffectedPolicySelectProps>( ), append: ( <LinkToApp - href={formatUrl(getPolicyDetailPath(policy.id))} - appId={MANAGEMENT_APP_ID} + href={getAppUrl({ path: getPolicyDetailPath(policy.id) })} appPath={getPolicyDetailPath(policy.id)} target="_blank" > @@ -106,7 +103,7 @@ export const EffectedPolicySelect = memo<EffectedPolicySelectProps>( 'data-test-subj': `policy-${policy.id}`, })) .sort(({ label: labelA }, { label: labelB }) => labelA.localeCompare(labelB)); - }, [formatUrl, isGlobal, options, selected]); + }, [getAppUrl, isGlobal, options, selected]); const handleOnPolicySelectChange = useCallback< Required<EuiSelectableProps<OptionPolicyData>>['onChange'] diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx index 4ed9a3c5a0119..74f3f0524b304 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.test.tsx @@ -85,7 +85,9 @@ describe('TrustedAppsGrid', () => { createListLoadedResourceState({ pageSize: 10 }, now) ) ); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); expect(renderList(store).container).toMatchSnapshot(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/index.test.tsx index d054061dbba31..64efda2c90ed1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/index.test.tsx @@ -92,7 +92,9 @@ describe('TrustedAppsList', () => { createListLoadedResourceState({ pageSize: 20 }, now) ) ); - store.dispatch(createUserChangedUrlAction('/trusted_apps', '?page_index=2&page_size=50')); + store.dispatch( + createUserChangedUrlAction('/administration/trusted_apps', '?page_index=2&page_size=50') + ); expect(renderList(store).container).toMatchSnapshot(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index adc9438f27d74..970ade80bd8db 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -144,7 +144,7 @@ describe('When on the Trusted Apps Page', () => { waitForAction = mockedContext.middlewareSpy.waitForAction; render = () => mockedContext.render(<TrustedAppsPage />); reactTestingLibrary.act(() => { - history.push('/trusted_apps'); + history.push('/administration/trusted_apps'); }); window.scrollTo = jest.fn(); }); @@ -305,7 +305,7 @@ describe('When on the Trusted Apps Page', () => { }); reactTestingLibrary.act(() => { - history.push('/trusted_apps?show=edit&id=9999-edit-8888'); + history.push('/administration/trusted_apps?show=edit&id=9999-edit-8888'); }); }); @@ -323,7 +323,7 @@ describe('When on the Trusted Apps Page', () => { it('should redirect to list and show toast message if `id` is missing from URL', async () => { reactTestingLibrary.act(() => { - history.push('/trusted_apps?show=edit&id='); + history.push('/administration/trusted_apps?show=edit&id='); }); await renderAndWaitForGetApi(); @@ -367,7 +367,7 @@ describe('When on the Trusted Apps Page', () => { beforeEach(async () => { reactTestingLibrary.act(() => { - history.push('/trusted_apps?view_type=list'); + history.push('/administration/trusted_apps?view_type=list'); }); renderResult = await renderWithListData(); @@ -477,7 +477,7 @@ describe('When on the Trusted Apps Page', () => { it('should preserve other URL search params', async () => { reactTestingLibrary.act(() => { - history.push('/trusted_apps?page_index=2&page_size=20'); + history.push('/administration/trusted_apps?page_index=2&page_size=20'); }); await renderAndClickAddButton(); expect(history.location.search).toBe('?page_index=2&page_size=20&show=create'); @@ -884,7 +884,7 @@ describe('When on the Trusted Apps Page', () => { beforeEach(async () => { mockListApis(coreStart.http); reactTestingLibrary.act(() => { - history.push('/trusted_apps?filter=test'); + history.push('/administration/trusted_apps?filter=test'); }); renderResult = render(); await act(async () => { @@ -912,7 +912,7 @@ describe('When on the Trusted Apps Page', () => { await waitForAction('trustedAppsListResourceStateChanged'); }); reactTestingLibrary.act(() => { - history.push('/trusted_apps', { + history.push('/administration/trusted_apps', { onBackButtonNavigateTo: [{ appId: 'appId' }], backButtonLabel: 'back to fleet', backButtonUrl: '/fleet', @@ -928,7 +928,7 @@ describe('When on the Trusted Apps Page', () => { it('back button is not present', () => { reactTestingLibrary.act(() => { - history.push('/trusted_apps'); + history.push('/administration/trusted_apps'); }); expect(renderResult.queryByTestId('backToOrigin')).toBeNull(); }); diff --git a/x-pack/plugins/security_solution/public/management/routes.tsx b/x-pack/plugins/security_solution/public/management/routes.tsx index b9143a862e736..bbc165d51a46c 100644 --- a/x-pack/plugins/security_solution/public/management/routes.tsx +++ b/x-pack/plugins/security_solution/public/management/routes.tsx @@ -6,19 +6,26 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { MANAGEMENT_PATH, SecurityPageName } from '../../common/constants'; import { ManagementContainer } from './pages'; -import { NotFoundPage } from '../app/404'; +import { SecuritySubPluginRoutes } from '../app/types'; import { CurrentLicense } from '../common/components/current_license'; /** * Returns the React Router Routes for the management area */ -export const ManagementRoutes = () => ( - <CurrentLicense> - <Switch> - <Route path="/" component={ManagementContainer} /> - <Route render={() => <NotFoundPage />} /> - </Switch> - </CurrentLicense> +const ManagementRoutes = () => ( + <TrackApplicationView viewId={SecurityPageName.administration}> + <CurrentLicense> + <ManagementContainer /> + </CurrentLicense> + </TrackApplicationView> ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: MANAGEMENT_PATH, + render: ManagementRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/network/index.ts b/x-pack/plugins/security_solution/public/network/index.ts index 5764484a53987..f34ebcc6e33b9 100644 --- a/x-pack/plugins/security_solution/public/network/index.ts +++ b/x-pack/plugins/security_solution/public/network/index.ts @@ -7,7 +7,7 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { SecuritySubPluginWithStore } from '../app/types'; -import { NetworkRoutes } from './routes'; +import { routes } from './routes'; import { initialNetworkState, networkReducer, NetworkState } from './store'; import { TimelineId } from '../../common/types/timeline'; import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; @@ -17,7 +17,7 @@ export class Network { public start(storage: Storage): SecuritySubPluginWithStore<'network', NetworkState> { return { - SubPluginRoutes: NetworkRoutes, + routes, storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]), }, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/utils.ts b/x-pack/plugins/security_solution/public/network/pages/details/utils.ts index bfba7fa938a81..637180203c6d1 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/network/pages/details/utils.ts @@ -36,7 +36,8 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.network}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.network, path: !isEmpty(search[0]) ? search[0] : '', }), }, @@ -46,7 +47,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: decodeIpv6(params.detailName), - href: getUrlForApp(`${APP_ID}:${SecurityPageName.network}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.network, path: getNetworkDetailsUrl( params.detailName, params.flowTarget, diff --git a/x-pack/plugins/security_solution/public/network/pages/index.tsx b/x-pack/plugins/security_solution/public/network/pages/index.tsx index ddc098823470a..965d461665ec2 100644 --- a/x-pack/plugins/security_solution/public/network/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/index.tsx @@ -6,7 +6,7 @@ */ import React, { useMemo } from 'react'; -import { Route, Switch, RouteComponentProps, useHistory } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; import { useMlCapabilities } from '../../common/components/ml/hooks/use_ml_capabilities'; import { hasMlUserPermissions } from '../../../common/machine_learning/has_ml_user_permissions'; @@ -17,14 +17,11 @@ import { getNetworkRoutePath } from './navigation'; import { NetworkRouteType } from './navigation/types'; import { MlNetworkConditionalContainer } from '../../common/components/ml/conditional_links/ml_network_conditional_container'; import { FlowTarget } from '../../../common/search_strategy'; +import { NETWORK_PATH } from '../../../common/constants'; -type Props = Partial<RouteComponentProps<{}>> & { url: string }; +const ipDetailsPageBasePath = `${NETWORK_PATH}/ip/:detailName`; -const networkPagePath = ''; -const ipDetailsPageBasePath = `/ip/:detailName`; - -const NetworkContainerComponent: React.FC<Props> = () => { - const history = useHistory(); +const NetworkContainerComponent = () => { const capabilities = useMlCapabilities(); const capabilitiesFetched = capabilities.capabilitiesFetched; const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [ @@ -38,14 +35,18 @@ const NetworkContainerComponent: React.FC<Props> = () => { return ( <Switch> <Route - path="/ml-network" - render={({ location, match }) => ( - <MlNetworkConditionalContainer location={location} url={match.url} /> + exact + strict + path={NETWORK_PATH} + render={({ location: { search = '' } }) => ( + <Redirect to={{ pathname: `${NETWORK_PATH}/${NetworkRouteType.flows}`, search }} /> )} /> + <Route path={`${NETWORK_PATH}/ml-network`}> + <MlNetworkConditionalContainer /> + </Route> <Route strict path={networkRoutePath}> <Network - networkPagePath={networkPagePath} capabilitiesFetched={capabilities.capabilitiesFetched} hasMlUserPermissions={userHasMlUserPermissions} /> @@ -60,17 +61,14 @@ const NetworkContainerComponent: React.FC<Props> = () => { match: { params: { detailName }, }, - }) => { - history.replace(`ip/${detailName}/${FlowTarget.source}${search}`); - return null; - }} - /> - <Route - path="/" - render={({ location: { search = '' } }) => { - history.replace(`${NetworkRouteType.flows}${search}`); - return null; - }} + }) => ( + <Redirect + to={{ + pathname: `${NETWORK_PATH}/ip/${detailName}/${FlowTarget.source}`, + search, + }} + /> + )} /> </Switch> ); diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx index 8e07cba1f5c1a..607b2e02ac961 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/nav_tabs.tsx @@ -8,9 +8,9 @@ import { omit } from 'lodash/fp'; import * as i18n from '../translations'; import { NetworkNavTab, NetworkRouteType } from './types'; -import { SecurityPageName } from '../../../app/types'; +import { NETWORK_PATH } from '../../../../common/constants'; -const getTabsOnNetworkUrl = (tabName: NetworkRouteType) => `/${tabName}`; +const getTabsOnNetworkUrl = (tabName: NetworkRouteType) => `${NETWORK_PATH}/${tabName}`; export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab => { const networkNavTabs = { @@ -19,48 +19,36 @@ export const navTabsNetwork = (hasMlUserPermissions: boolean): NetworkNavTab => name: i18n.NAVIGATION_FLOWS_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.flows), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, [NetworkRouteType.dns]: { id: NetworkRouteType.dns, name: i18n.NAVIGATION_DNS_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.dns), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, [NetworkRouteType.http]: { id: NetworkRouteType.http, name: i18n.NAVIGATION_HTTP_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.http), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, [NetworkRouteType.tls]: { id: NetworkRouteType.tls, name: i18n.NAVIGATION_TLS_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.tls), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, [NetworkRouteType.anomalies]: { id: NetworkRouteType.anomalies, name: i18n.NAVIGATION_ANOMALIES_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.anomalies), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, [NetworkRouteType.alerts]: { id: NetworkRouteType.alerts, name: i18n.NAVIGATION_ALERTS_TITLE, href: getTabsOnNetworkUrl(NetworkRouteType.alerts), disabled: false, - urlKey: 'network', - pageId: SecurityPageName.network, }, }; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx index a1d012d64d7b7..ea026664ce1e4 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx @@ -24,10 +24,10 @@ import { TlsQueryTabBody } from './tls_query_tab_body'; import { Anomaly } from '../../../common/components/ml/types'; import { NetworkAlertsQueryTabBody } from './alerts_query_tab_body'; import { UpdateDateRange } from '../../../common/components/charts/common'; +import { NETWORK_PATH } from '../../../../common/constants'; export const NetworkRoutes = React.memo<NetworkRoutesProps>( ({ - networkPagePath, docValueFields, type, to, @@ -108,10 +108,10 @@ export const NetworkRoutes = React.memo<NetworkRoutesProps>( return ( <Switch> - <Route path={`/:tabName(${NetworkRouteType.dns})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.dns})`}> <DnsQueryTabBody {...tabProps} docValueFields={docValueFields} /> </Route> - <Route path={`/:tabName(${NetworkRouteType.flows})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.flows})`}> <> <ConditionalFlexGroup direction="column"> <EuiFlexItem> @@ -137,19 +137,19 @@ export const NetworkRoutes = React.memo<NetworkRoutesProps>( </ConditionalFlexGroup> </> </Route> - <Route path={`/:tabName(${NetworkRouteType.http})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.http})`}> <HttpQueryTabBody {...tabProps} /> </Route> - <Route path={`/:tabName(${NetworkRouteType.tls})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.tls})`}> <TlsQueryTabBody {...tabProps} flowTarget={FlowTargetSourceDest.source} /> </Route> - <Route path={`/:tabName(${NetworkRouteType.anomalies})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.anomalies})`}> <AnomaliesQueryTabBody {...anomaliesProps} AnomaliesTableComponent={AnomaliesNetworkTable} /> </Route> - <Route path={`/:tabName(${NetworkRouteType.alerts})`}> + <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.alerts})`}> <NetworkAlertsQueryTabBody {...tabProps} /> </Route> </Switch> diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts index ed1682e38da9a..075aa46637a07 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/types.ts @@ -47,7 +47,6 @@ export type HttpQueryTabBodyProps = QueryTabBodyProps & { export type NetworkRoutesProps = GlobalTimeArgs & { docValueFields: DocValueFields[]; - networkPagePath: string; type: networkModel.NetworkType; filterQuery?: string | ESTermQuery; indexPattern: IIndexPattern; diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts b/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts index 8f2de2b0c9812..fcd81ca975584 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { NETWORK_PATH } from '../../../../common/constants'; import { GetNetworkRoutePath, NetworkRouteType } from './types'; export const getNetworkRoutePath: GetNetworkRoutePath = ( @@ -12,11 +13,11 @@ export const getNetworkRoutePath: GetNetworkRoutePath = ( hasMlUserPermission ) => { if (capabilitiesFetched && !hasMlUserPermission) { - return `/:tabName(${NetworkRouteType.flows}|${NetworkRouteType.dns}|${NetworkRouteType.http}|${NetworkRouteType.tls}|${NetworkRouteType.alerts})`; + return `${NETWORK_PATH}/:tabName(${NetworkRouteType.flows}|${NetworkRouteType.dns}|${NetworkRouteType.http}|${NetworkRouteType.tls}|${NetworkRouteType.alerts})`; } return ( - `/:tabName(` + + `${NETWORK_PATH}/:tabName(` + `${NetworkRouteType.flows}|` + `${NetworkRouteType.dns}|` + `${NetworkRouteType.anomalies}|` + diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index 13c04a5e5ec5b..b08a75215a408 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -62,7 +62,7 @@ const StyledFullHeightContainer = styled.div` `; const NetworkComponent = React.memo<NetworkComponentProps>( - ({ networkPagePath, hasMlUserPermissions, capabilitiesFetched }) => { + ({ hasMlUserPermissions, capabilitiesFetched }) => { const dispatch = useDispatch(); const containerElement = useRef<HTMLDivElement | null>(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); @@ -193,9 +193,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>( <> <Display show={!globalFullScreen}> <EuiSpacer /> - <SecuritySolutionTabNavigation navTabs={navTabsNetwork(hasMlUserPermissions)} /> - <EuiSpacer /> </Display> @@ -210,7 +208,6 @@ const NetworkComponent = React.memo<NetworkComponentProps>( setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker} type={networkModel.NetworkType.page} to={to} - networkPagePath={networkPagePath} /> </> ) : ( diff --git a/x-pack/plugins/security_solution/public/network/pages/types.ts b/x-pack/plugins/security_solution/public/network/pages/types.ts index 1d727a2d219d7..df5ca5656abfb 100644 --- a/x-pack/plugins/security_solution/public/network/pages/types.ts +++ b/x-pack/plugins/security_solution/public/network/pages/types.ts @@ -16,7 +16,6 @@ export type SetAbsoluteRangeDatePicker = ActionCreator<{ }>; export type NetworkComponentProps = Partial<RouteComponentProps<{}>> & { - networkPagePath: string; hasMlUserPermissions: boolean; capabilitiesFetched: boolean; }; diff --git a/x-pack/plugins/security_solution/public/network/routes.tsx b/x-pack/plugins/security_solution/public/network/routes.tsx index 9704c92e19336..32fbe96a21caa 100644 --- a/x-pack/plugins/security_solution/public/network/routes.tsx +++ b/x-pack/plugins/security_solution/public/network/routes.tsx @@ -6,17 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - import { NetworkContainer } from './pages'; -import { NotFoundPage } from '../app/404'; + +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { NETWORK_PATH } from '../../common/constants'; export const NetworkRoutes = () => ( - <Switch> - <Route - path="/" - render={({ location, match }) => <NetworkContainer location={location} url={match.url} />} - /> - <Route render={() => <NotFoundPage />} /> - </Switch> + <TrackApplicationView viewId={SecurityPageName.network}> + <NetworkContainer /> + </TrackApplicationView> ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: NETWORK_PATH, + render: NetworkRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index 9957d43551ff9..98874c25e0ef8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -67,7 +67,8 @@ const AlertsByCategoryComponent: React.FC<Props> = ({ const goToHostAlerts = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, path: getTabsOnHostsUrl(HostsTableType.alerts, urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx index e908748d0028c..6a00afde7c599 100644 --- a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx @@ -8,15 +8,16 @@ import React, { memo } from 'react'; import { EuiCallOut, EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../common/lib/kibana'; +import { APP_ID } from '../../../../common/constants'; import { getEndpointListPath } from '../../../management/common/routing'; import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { useManagementFormatUrl } from '../../../management/components/hooks/use_management_format_url'; -import { MANAGEMENT_APP_ID } from '../../../management/common/constants'; export const EndpointNotice = memo<{ onDismiss: () => void }>(({ onDismiss }) => { + const { getUrlForApp } = useKibana().services.application; const endpointsPath = getEndpointListPath({ name: 'endpointList' }); - const endpointsLink = useManagementFormatUrl(endpointsPath); - const handleGetStartedClick = useNavigateToAppEventHandler(MANAGEMENT_APP_ID, { + const endpointsLink = getUrlForApp(APP_ID, { path: endpointsPath }); + const handleGetStartedClick = useNavigateToAppEventHandler(APP_ID, { path: endpointsPath, }); diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 8a1d1c67174fc..a6ebfd2bbe060 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -96,7 +96,8 @@ const EventsByDatasetComponent: React.FC<Props> = ({ const goToHostEvents = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, path: getTabsOnHostsUrl(HostsTableType.events, urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index f11b849f5df6b..0a8e817a3bfc4 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -16,7 +16,7 @@ import { ESQuery } from '../../../../common/typed_json'; import { ID as OverviewHostQueryId, useHostOverview } from '../../containers/overview_host'; import { HeaderSection } from '../../../common/components/header_section'; import { useUiSetting$, useKibana } from '../../../common/lib/kibana'; -import { getHostsUrl, useFormatUrl } from '../../../common/components/link_to'; +import { getHostDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; import { getOverviewHostStats, OverviewHostStats } from '../overview_host_stats'; import { manageQuery } from '../../../common/components/page/manage_query'; import { InspectButtonContainer } from '../../../common/components/inspect'; @@ -56,8 +56,9 @@ const OverviewHostComponent: React.FC<OverviewHostProps> = ({ const goToHost = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, { - path: getHostsUrl(urlSearch), + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.hosts, + path: getHostDetailsUrl('allHosts', urlSearch), }); }, [navigateToApp, urlSearch] @@ -75,7 +76,7 @@ const OverviewHostComponent: React.FC<OverviewHostProps> = ({ const hostPageButton = useMemo( () => ( - <LinkButton onClick={goToHost} href={formatUrl(getHostsUrl())}> + <LinkButton onClick={goToHost} href={formatUrl('/allHosts')}> <FormattedMessage id="xpack.securitySolution.overview.hostsAction" defaultMessage="View hosts" diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index 13a9b529fdf43..08b2392f60488 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -20,6 +20,7 @@ import { import { OverviewNetwork } from '.'; import { createStore, State } from '../../../common/store'; import { useNetworkOverview } from '../../containers/overview_network'; +import { SecurityPageName } from '../../../app/types'; jest.mock('../../../common/components/link_to'); const mockNavigateToApp = jest.fn(); @@ -137,6 +138,9 @@ describe('OverviewNetwork', () => { preventDefault: jest.fn(), }); - expect(mockNavigateToApp).toBeCalledWith('securitySolution:network', { path: '' }); + expect(mockNavigateToApp).toBeCalledWith('securitySolution', { + path: '', + deepLinkId: SecurityPageName.network, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index 39fb6ff08ee53..eb5231d4ce5e0 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -58,7 +58,8 @@ const OverviewNetworkComponent: React.FC<OverviewNetworkProps> = ({ const goToNetwork = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.network}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.network, path: getNetworkUrl(urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx index cb7733e304985..207c6ef16bd16 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx @@ -14,7 +14,7 @@ import { } from '../../../common/components/link_to/redirect_to_case'; import { useFormatUrl } from '../../../common/components/link_to'; import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; import { AllCasesNavProps } from '../../../cases/components/all_cases'; @@ -32,10 +32,8 @@ const RecentCasesComponent = () => { allCasesNavigation: { href: formatUrl(getCaseUrl()), onClick: async (e) => { - if (e) { - e.preventDefault(); - } - return navigateToApp(CASES_APP_ID); + e?.preventDefault(); + return navigateToApp(APP_ID, { deepLinkId: SecurityPageName.case }); }, }, caseDetailsNavigation: { @@ -43,10 +41,9 @@ const RecentCasesComponent = () => { return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, onClick: async ({ detailName, subCaseId, search }, e) => { - if (e) { - e.preventDefault(); - } - return navigateToApp(CASES_APP_ID, { + e?.preventDefault(); + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, @@ -54,10 +51,9 @@ const RecentCasesComponent = () => { createCaseNavigation: { href: formatUrl(getCreateCaseUrl()), onClick: async (e) => { - if (e) { - e.preventDefault(); - } - return navigateToApp(CASES_APP_ID, { + e?.preventDefault(); + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(), }); }, diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx index 1d9b039e02258..f76d71600d0e7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx @@ -60,7 +60,9 @@ const StatefulRecentTimelinesComponent: React.FC<Props> = ({ filterBy }) => { const goToTimelines = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, + }); }, [navigateToApp] ); diff --git a/x-pack/plugins/security_solution/public/overview/index.ts b/x-pack/plugins/security_solution/public/overview/index.ts index 4ec5b6d7ce48b..3aa6c4185f6da 100644 --- a/x-pack/plugins/security_solution/public/overview/index.ts +++ b/x-pack/plugins/security_solution/public/overview/index.ts @@ -6,14 +6,14 @@ */ import { SecuritySubPlugin } from '../app/types'; -import { OverviewRoutes } from './routes'; +import { routes } from './routes'; export class Overview { public setup() {} public start(): SecuritySubPlugin { return { - SubPluginRoutes: OverviewRoutes, + routes, }; } } diff --git a/x-pack/plugins/security_solution/public/overview/routes.tsx b/x-pack/plugins/security_solution/public/overview/routes.tsx index 7d6fc4858c670..0f83c03f7e3d9 100644 --- a/x-pack/plugins/security_solution/public/overview/routes.tsx +++ b/x-pack/plugins/security_solution/public/overview/routes.tsx @@ -6,14 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { OVERVIEW_PATH, SecurityPageName } from '../../common/constants'; +import { SecuritySubPluginRoutes } from '../app/types'; import { Overview } from './pages'; -import { NotFoundPage } from '../app/404'; -export const OverviewRoutes = () => ( - <Switch> - <Route path="/" render={() => <Overview />} /> - <Route render={() => <NotFoundPage />} /> - </Switch> +const OverviewRoutes = () => ( + <TrackApplicationView viewId={SecurityPageName.overview}> + <Overview /> + </TrackApplicationView> ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: OVERVIEW_PATH, + render: OverviewRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 32e6748f38141..1bf3edf1605d8 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -18,6 +18,7 @@ import { StartServices, AppObservableLibs, SubPlugins, + StartedSubPlugins, } from './types'; import { AppMountParameters, @@ -35,33 +36,17 @@ import { KibanaServices } from './common/lib/kibana/services'; import { APP_ID, - APP_ICON_SOLUTION, - APP_DETECTIONS_PATH, - APP_HOSTS_PATH, + OVERVIEW_PATH, APP_OVERVIEW_PATH, - APP_NETWORK_PATH, - APP_TIMELINES_PATH, - APP_MANAGEMENT_PATH, - APP_CASES_PATH, APP_PATH, - CASES_APP_ID, DEFAULT_INDEX_KEY, DETECTION_ENGINE_INDEX_URL, DEFAULT_ALERTS_INDEX, + APP_ICON_SOLUTION, } from '../common/constants'; -import { SecurityPageName } from './app/types'; -import { registerDeepLinks, getDeepLinksAndKeywords } from './app/search'; +import { getDeepLinks, updateGlobalNavigation } from './app/deep_links'; import { manageOldSiemRoutes } from './helpers'; -import { - OVERVIEW, - HOSTS, - NETWORK, - TIMELINES, - DETECTION_ENGINE, - CASE, - ADMINISTRATION, -} from './app/translations'; import { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, @@ -84,10 +69,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S this.config = this.initializerContext.config.get<SecuritySolutionUiConfigType>(); this.kibanaVersion = initializerContext.env.packageInfo.version; } - private detectionsUpdater$ = new Subject<AppUpdater>(); - private hostsUpdater$ = new Subject<AppUpdater>(); - private networkUpdater$ = new Subject<AppUpdater>(); - private caseUpdater$ = new Subject<AppUpdater>(); + private appUpdater$ = new Subject<AppUpdater>(); private storage = new Storage(localStorage); private licensingSubscription: Subscription | null = null; @@ -159,162 +141,26 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S })(); core.application.register({ - exactRoute: true, id: APP_ID, title: APP_NAME, appRoute: APP_PATH, - navLinkStatus: AppNavLinkStatus.hidden, - mount: async () => { - const [{ application }] = await core.getStartServices(); - application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { replace: true }); - return () => true; - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.overview}`, - title: OVERVIEW, - order: 9000, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_OVERVIEW_PATH, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { overview: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start().SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.detections}`, - title: DETECTION_ENGINE, - order: 9001, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_DETECTIONS_PATH, - updater$: this.detectionsUpdater$, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { detections: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start(this.storage).SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.hosts}`, - title: HOSTS, - order: 9002, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_HOSTS_PATH, - updater$: this.hostsUpdater$, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { hosts: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start(this.storage).SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.network}`, - title: NETWORK, - order: 9002, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_NETWORK_PATH, - updater$: this.networkUpdater$, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { network: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start(this.storage).SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.timelines}`, - title: TIMELINES, - order: 9002, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_TIMELINES_PATH, - ...getDeepLinksAndKeywords(SecurityPageName.timelines), - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { timelines: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start().SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: CASES_APP_ID, - title: CASE, - order: 9002, - euiIconType: APP_ICON_SOLUTION, category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_CASES_PATH, - updater$: this.caseUpdater$, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { cases: subPlugin } = await this.subPlugins(); - const { renderApp } = await this.lazyApplicationDependencies(); - return renderApp({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start().SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: `${APP_ID}:${SecurityPageName.administration}`, - title: ADMINISTRATION, - order: 9002, + navLinkStatus: AppNavLinkStatus.hidden, + searchable: true, + defaultPath: OVERVIEW_PATH, + updater$: this.appUpdater$, euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_MANAGEMENT_PATH, - ...getDeepLinksAndKeywords(SecurityPageName.administration), + deepLinks: getDeepLinks(), mount: async (params: AppMountParameters) => { const [coreStart, startPlugins] = await core.getStartServices(); - const { management: managementSubPlugin } = await this.subPlugins(); + const subPlugins = await this.startSubPlugins(this.storage, coreStart, startPlugins); const { renderApp } = await this.lazyApplicationDependencies(); return renderApp({ ...params, services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: managementSubPlugin.start(coreStart, startPlugins).SubPluginRoutes, + store: await this.store(coreStart, startPlugins, subPlugins), + usageCollection: plugins.usageCollection, + subPlugins, }); }, }); @@ -376,17 +222,19 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S if (licensing !== null) { this.licensingSubscription = licensing.subscribe((currentLicense) => { if (currentLicense.type !== undefined) { - registerDeepLinks(SecurityPageName.network, this.networkUpdater$, currentLicense.type); - registerDeepLinks( - SecurityPageName.detections, - this.detectionsUpdater$, - currentLicense.type - ); - registerDeepLinks(SecurityPageName.hosts, this.hostsUpdater$, currentLicense.type); - registerDeepLinks(SecurityPageName.case, this.caseUpdater$, currentLicense.type); + this.appUpdater$.next(() => ({ + navLinkStatus: AppNavLinkStatus.hidden, // workaround to prevent main navLink to switch to visible after update. should not be needed + deepLinks: getDeepLinks(currentLicense.type, core.application.capabilities), + })); } }); + } else { + updateGlobalNavigation({ + capabilities: core.application.capabilities, + updater$: this.appUpdater$, + }); } + return {}; } @@ -434,7 +282,9 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S if (!this._subPlugins) { const { subPluginClasses } = await this.lazySubPlugins(); this._subPlugins = { - detections: new subPluginClasses.Detections(), + alerts: new subPluginClasses.Detections(), + rules: new subPluginClasses.Rules(), + exceptions: new subPluginClasses.Exceptions(), cases: new subPluginClasses.Cases(), hosts: new subPluginClasses.Hosts(), network: new subPluginClasses.Network(), @@ -446,10 +296,36 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S return this._subPlugins; } + /** + * All started subPlugins. + */ + private async startSubPlugins( + storage: Storage, + core: CoreStart, + plugins: StartPlugins + ): Promise<StartedSubPlugins> { + const subPlugins = await this.subPlugins(); + return { + overview: subPlugins.overview.start(), + alerts: subPlugins.alerts.start(storage), + rules: subPlugins.rules.start(storage), + exceptions: subPlugins.exceptions.start(storage), + cases: subPlugins.cases.start(), + hosts: subPlugins.hosts.start(storage), + network: subPlugins.network.start(storage), + timelines: subPlugins.timelines.start(), + management: subPlugins.management.start(core, plugins), + }; + } + /** * Lazily instantiate a `SecurityAppStore`. We lazily instantiate this because it requests large dynamic imports. We instantiate it once because each subPlugin needs to share the same reference. */ - private async store(coreStart: CoreStart, startPlugins: StartPlugins): Promise<SecurityAppStore> { + private async store( + coreStart: CoreStart, + startPlugins: StartPlugins, + subPlugins: StartedSubPlugins + ): Promise<SecurityAppStore> { if (!this._store) { const experimentalFeatures = parseExperimentalConfigValue( this.config.enableExperimental || [] @@ -458,18 +334,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S const [ { createStore, createInitialState }, kibanaIndexPatterns, - { - detections: detectionsSubPlugin, - hosts: hostsSubPlugin, - network: networkSubPlugin, - timelines: timelinesSubPlugin, - management: managementSubPlugin, - }, configIndexPatterns, ] = await Promise.all([ this.lazyApplicationDependencies(), startPlugins.data.indexPatterns.getIdsWithTitle(), - this.subPlugins(), startPlugins.data.search .search<IndexFieldsStrategyRequest, IndexFieldsStrategyResponse>( { indices: defaultIndicesName, onlyCheckIfIndicesExist: true }, @@ -498,19 +366,16 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S const appLibs: AppObservableLibs = { kibana: coreStart }; const libs$ = new BehaviorSubject(appLibs); - const detectionsStart = detectionsSubPlugin.start(this.storage); - const hostsStart = hostsSubPlugin.start(this.storage); - const networkStart = networkSubPlugin.start(this.storage); - const timelinesStart = timelinesSubPlugin.start(); - const managementSubPluginStart = managementSubPlugin.start(coreStart, startPlugins); const timelineInitialState = { timeline: { - ...timelinesStart.store.initialState.timeline!, + ...subPlugins.timelines.store.initialState.timeline!, timelineById: { - ...timelinesStart.store.initialState.timeline!.timelineById, - ...detectionsStart.storageTimelines!.timelineById, - ...hostsStart.storageTimelines!.timelineById, - ...networkStart.storageTimelines!.timelineById, + ...subPlugins.timelines.store.initialState.timeline!.timelineById, + ...subPlugins.alerts.storageTimelines!.timelineById, + ...subPlugins.rules.storageTimelines!.timelineById, + ...subPlugins.exceptions.storageTimelines!.timelineById, + ...subPlugins.hosts.storageTimelines!.timelineById, + ...subPlugins.network.storageTimelines!.timelineById, }, }, }; @@ -519,16 +384,16 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S const timelineReducer = (reduceReducers( timelineInitialState.timeline, tGridReducer, - timelinesStart.store.reducer.timeline + subPlugins.timelines.store.reducer.timeline ) as unknown) as Reducer<TimelineState, AnyAction>; this._store = createStore( createInitialState( { - ...hostsStart.store.initialState, - ...networkStart.store.initialState, + ...subPlugins.hosts.store.initialState, + ...subPlugins.network.store.initialState, ...timelineInitialState, - ...managementSubPluginStart.store.initialState, + ...subPlugins.management.store.initialState, }, { kibanaIndexPatterns, @@ -538,15 +403,15 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S } ), { - ...hostsStart.store.reducer, - ...networkStart.store.reducer, + ...subPlugins.hosts.store.reducer, + ...subPlugins.network.store.reducer, timeline: timelineReducer, - ...managementSubPluginStart.store.reducer, + ...subPlugins.management.store.reducer, ...tGridReducer, }, libs$.pipe(pluck('kibana')), this.storage, - [...(managementSubPluginStart.store.middleware ?? [])] + [...(subPlugins.management.store.middleware ?? [])] ); if (startPlugins.timelines) { startPlugins.timelines.setTGridEmbeddedStore(this._store); diff --git a/x-pack/plugins/security_solution/public/rules/index.ts b/x-pack/plugins/security_solution/public/rules/index.ts new file mode 100644 index 0000000000000..e74efabad4932 --- /dev/null +++ b/x-pack/plugins/security_solution/public/rules/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; + +import { SecuritySubPlugin } from '../app/types'; +import { DETECTIONS_TIMELINE_IDS } from '../detections'; +import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage'; +import { routes } from './routes'; + +export class Rules { + public setup() {} + + public start(storage: Storage): SecuritySubPlugin { + return { + storageTimelines: { + timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), + }, + routes, + }; + } +} diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx new file mode 100644 index 0000000000000..39b882ad76f8c --- /dev/null +++ b/x-pack/plugins/security_solution/public/rules/routes.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { RULES_PATH, SecurityPageName } from '../../common/constants'; +import { RulesPage } from '../detections/pages/detection_engine/rules'; +import { CreateRulePage } from '../detections/pages/detection_engine/rules/create'; +import { RuleDetailsPage } from '../detections/pages/detection_engine/rules/details'; +import { EditRulePage } from '../detections/pages/detection_engine/rules/edit'; + +const RulesSubRoutes = [ + { + path: '/rules/id/:detailName/edit', + main: EditRulePage, + }, + { + path: '/rules/id/:detailName', + main: RuleDetailsPage, + }, + { + path: '/rules/create', + main: CreateRulePage, + }, + { + path: '/rules', + exact: true, + main: RulesPage, + }, +]; + +export const RulesRoutes = () => { + return ( + <TrackApplicationView viewId={SecurityPageName.rules}> + <Switch> + {RulesSubRoutes.map((route, index) => ( + <Route key={`rules-route-${route.path}`} path={route.path} exact={route?.exact ?? false}> + <route.main /> + </Route> + ))} + </Switch> + </TrackApplicationView> + ); +}; + +export const routes = [ + { + path: RULES_PATH, + render: RulesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx index bc9876b207284..717b338aa2535 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx @@ -12,6 +12,7 @@ import { useKibana } from '../../../../common/lib/kibana'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; import { AddToCaseButton } from '.'; +import { SecurityPageName } from '../../../../../common/constants'; jest.mock('../../../../common/components/link_to', () => { const original = jest.requireActual('../../../../common/components/link_to'); @@ -61,7 +62,10 @@ describe('AddToCaseButton', () => { wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/create' }); + expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + path: '/create', + deepLinkId: SecurityPageName.case, + }); }); it('navigates to the correct path with id', async () => { @@ -80,6 +84,9 @@ describe('AddToCaseButton', () => { wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution:case', { path: '/case-id' }); + expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + path: '/case-id', + deepLinkId: SecurityPageName.case, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx index 47935347b96ac..553b827f2a64c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx @@ -11,7 +11,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Case, SubCase } from '../../../../../../cases/common'; -import { APP_ID, CASES_APP_ID } from '../../../../../common/constants'; +import { APP_ID } from '../../../../../common/constants'; import { timelineSelectors } from '../../../../timelines/store/timeline'; import { setInsertTimeline, showTimeline } from '../../../store/timeline/actions'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -55,7 +55,8 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { const onRowClick = useCallback( async (theCase?: Case | SubCase) => { openCaseModal(false); - await navigateToApp(CASES_APP_ID, { + await navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: theCase != null ? getCaseDetailsUrl({ id: theCase.id }) : getCreateCaseUrl(), }); dispatch( @@ -88,7 +89,9 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { const handleNewCaseClick = useCallback(() => { handlePopoverClose(); - navigateToApp(CASES_APP_ID, { + + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(), }).then(() => { dispatch( diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx index 35a13aba471fd..14ebfbc20d9c9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlyout } from '@elastic/eui'; +import { EuiFlyout, EuiFlyoutProps } from '@elastic/eui'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; @@ -23,13 +23,8 @@ interface FlyoutPaneComponentProps { visible?: boolean; } -const EuiFlyoutContainer = styled.div` - .timeline-flyout { - z-index: ${({ theme }) => theme.eui.euiZLevel8}; - min-width: 150px; - width: 100%; - animation: none; - } +const StyledEuiFlyout = styled(EuiFlyout)<EuiFlyoutProps>` + animation: none; `; const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({ @@ -43,17 +38,14 @@ const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({ }, [dispatch, timelineId]); return ( - <EuiFlyoutContainer - data-test-subj="flyout-pane" - style={{ visibility: visible ? 'visible' : 'hidden' }} - > - <EuiFlyout + <div data-test-subj="flyout-pane" style={{ visibility: visible ? 'visible' : 'hidden' }}> + <StyledEuiFlyout aria-label={i18n.TIMELINE_DESCRIPTION} className="timeline-flyout" data-test-subj="eui-flyout" hideCloseButton={true} onClose={handleClose} - size="l" + size="100%" ownFocus={false} style={{ visibility: visible ? 'visible' : 'hidden' }} > @@ -62,8 +54,8 @@ const FlyoutPaneComponent: React.FC<FlyoutPaneComponentProps> = ({ rowRenderers={defaultRowRenderers} timelineId={timelineId} /> - </EuiFlyout> - </EuiFlyoutContainer> + </StyledEuiFlyout> + </div> ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx index 1d39dd169ffaa..5402210d22cce 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx @@ -31,6 +31,22 @@ jest.mock('../../../common/components/link_to', () => { }; }); +jest.mock('../../../../../../../src/plugins/kibana_react/public', () => { + const originalModule = jest.requireActual('../../../../../../../src/plugins/kibana_react/public'); + const useKibana = jest.fn().mockImplementation(() => ({ + services: { + application: { + navigateToUrl: jest.fn(), + }, + }, + })); + + return { + ...originalModule, + useKibana, + }; +}); + describe('useTimelineTypes', () => { it('init', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx index a66fe43d305f1..ca8b443309e12 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; import { noop } from 'lodash/fp'; @@ -15,7 +15,7 @@ import { SecurityPageName } from '../../../app/types'; import { getTimelineTabsUrl, useFormatUrl } from '../../../common/components/link_to'; import * as i18n from './translations'; import { TimelineTabsStyle, TimelineTab } from './types'; - +import { useKibana } from '../../../common/lib/kibana'; export interface UseTimelineTypesArgs { defaultTimelineCount?: number | null; templateTimelineCount?: number | null; @@ -31,8 +31,8 @@ export const useTimelineTypes = ({ defaultTimelineCount, templateTimelineCount, }: UseTimelineTypesArgs): UseTimelineTypesResult => { - const history = useHistory(); const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.timelines); + const { navigateToUrl } = useKibana().services.application; const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); const [timelineType, setTimelineTypes] = useState<TimelineTypeLiteralWithNull>( tabName === TimelineType.default || tabName === TimelineType.template @@ -40,27 +40,30 @@ export const useTimelineTypes = ({ : TimelineType.default ); + const timelineUrl = formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)); + const templateUrl = formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)); + const goToTimeline = useCallback( (ev) => { ev.preventDefault(); - history.push(getTimelineTabsUrl(TimelineType.default, urlSearch)); + navigateToUrl(timelineUrl); }, - [history, urlSearch] + [navigateToUrl, timelineUrl] ); const goToTemplateTimeline = useCallback( (ev) => { ev.preventDefault(); - history.push(getTimelineTabsUrl(TimelineType.template, urlSearch)); + navigateToUrl(templateUrl); }, - [history, urlSearch] + [navigateToUrl, templateUrl] ); const getFilterOrTabs: (timelineTabsStyle: TimelineTabsStyle) => TimelineTab[] = useCallback( (timelineTabsStyle: TimelineTabsStyle) => [ { id: TimelineType.default, name: i18n.TAB_TIMELINES, - href: formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)), + href: timelineUrl, disabled: false, onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTimeline : noop, @@ -68,13 +71,13 @@ export const useTimelineTypes = ({ { id: TimelineType.template, name: i18n.TAB_TEMPLATES, - href: formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)), + href: templateUrl, disabled: false, onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTemplateTimeline : noop, }, ], - [urlSearch, formatUrl, goToTimeline, goToTemplateTimeline] + [timelineUrl, templateUrl, goToTimeline, goToTemplateTimeline] ); const onFilterClicked = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 4c8139a78b012..06db698a91a6d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -274,12 +274,14 @@ Array [ <Styled(EuiFlyout) data-test-subj="timeline:details-panel:flyout" onClose={[Function]} + ownFocus={false} size="m" > <EuiFlyout className="c0" data-test-subj="timeline:details-panel:flyout" onClose={[Function]} + ownFocus={false} size="m" > <div @@ -507,6 +509,7 @@ Array [ className="c0" data-test-subj="timeline:details-panel:flyout" onClose={[Function]} + ownFocus={false} size="m" > <div diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx index 629bdcca98640..ea408be7c8e9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.tsx @@ -117,6 +117,7 @@ export const DetailsPanel = React.memo( data-test-subj="timeline:details-panel:flyout" size={panelSize} onClose={closePanel} + ownFocus={false} > {visiblePanel} </StyledEuiFlyout> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index d33192528a090..ef509cdfbda17 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -48,14 +48,15 @@ export const RenderRuleName: React.FC<RenderRuleNameProps> = ({ }) => { const ruleName = `${value}`; const ruleId = linkValue; - const { search } = useFormatUrl(SecurityPageName.detections); + const { search } = useFormatUrl(SecurityPageName.rules); const { navigateToApp, getUrlForApp } = useKibana().services.application; const content = truncate ? <TruncatableText>{value}</TruncatableText> : value; const goToRuleDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? '', search), }); }, @@ -71,7 +72,8 @@ export const RenderRuleName: React.FC<RenderRuleNameProps> = ({ > <LinkAnchor onClick={goToRuleDetails} - href={getUrlForApp(`${APP_ID}:${SecurityPageName.detections}`, { + href={getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId, search), })} > diff --git a/x-pack/plugins/security_solution/public/timelines/index.ts b/x-pack/plugins/security_solution/public/timelines/index.ts index 224eec7568c6b..8725072e61849 100644 --- a/x-pack/plugins/security_solution/public/timelines/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/index.ts @@ -6,7 +6,7 @@ */ import { SecuritySubPluginWithStore } from '../app/types'; -import { TimelinesRoutes } from './routes'; +import { routes } from './routes'; import { initialTimelineState, timelineReducer } from './store/timeline/reducer'; import { TimelineState } from './store/timeline/types'; @@ -15,7 +15,7 @@ export class Timelines { public start(): SecuritySubPluginWithStore<'timeline', TimelineState> { return { - SubPluginRoutes: TimelinesRoutes, + routes, store: { initialState: { timeline: initialTimelineState }, reducer: { timeline: timelineReducer }, diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx index 806ac57df1f65..2bf6e1259ff75 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash/fp'; import React from 'react'; -import { Switch, Route, useHistory } from 'react-router-dom'; +import { Switch, Route, Redirect } from 'react-router-dom'; import { ChromeBreadcrumb } from '../../../../../../src/core/public'; @@ -18,11 +18,11 @@ import { TimelinesPage } from './timelines_page'; import { PAGE_TITLE } from './translations'; import { appendSearch } from '../../common/components/link_to/helpers'; import { GetUrlForApp } from '../../common/components/navigation/types'; -import { APP_ID } from '../../../common/constants'; +import { APP_ID, TIMELINES_PATH } from '../../../common/constants'; import { SecurityPageName } from '../../app/types'; -const timelinesPagePath = `/:tabName(${TimelineType.default}|${TimelineType.template})`; -const timelinesDefaultPath = `/${TimelineType.default}`; +const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template})`; +const timelinesDefaultPath = `${TIMELINES_PATH}/${TimelineType.default}`; export const getBreadcrumbs = ( params: TimelineRouteSpyState, @@ -31,28 +31,25 @@ export const getBreadcrumbs = ( ): ChromeBreadcrumb[] => [ { text: PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.timelines}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, path: !isEmpty(search[0]) ? search[0] : '', }), }, ]; -export const Timelines = React.memo(() => { - const history = useHistory(); - return ( - <Switch> - <Route exact path={timelinesPagePath}> - <TimelinesPage /> - </Route> - <Route - path="/" - render={({ location: { search = '' } }) => { - history.replace(`${timelinesDefaultPath}${appendSearch(search)}`); - return null; - }} - /> - </Switch> - ); -}); +export const Timelines = React.memo(() => ( + <Switch> + <Route exact path={timelinesPagePath}> + <TimelinesPage /> + </Route> + <Route + path={TIMELINES_PATH} + render={({ location: { search = '' } }) => ( + <Redirect to={`${timelinesDefaultPath}${appendSearch(search)}`} /> + )} + /> + </Switch> +)); Timelines.displayName = 'Timelines'; diff --git a/x-pack/plugins/security_solution/public/timelines/routes.tsx b/x-pack/plugins/security_solution/public/timelines/routes.tsx index 010a022bfefb7..0bc95b3b4959f 100644 --- a/x-pack/plugins/security_solution/public/timelines/routes.tsx +++ b/x-pack/plugins/security_solution/public/timelines/routes.tsx @@ -6,20 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; import { Timelines } from './pages'; -import { NotFoundPage } from '../app/404'; +import { TIMELINES_PATH } from '../../common/constants'; + +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; -const TimelinesRoutesComponent = () => ( - <Switch> - <Route path="/"> - <Timelines /> - </Route> - <Route> - <NotFoundPage /> - </Route> - </Switch> +const TimelinesRoutes = () => ( + <TrackApplicationView viewId={SecurityPageName.timelines}> + <Timelines /> + </TrackApplicationView> ); -export const TimelinesRoutes = React.memo(TimelinesRoutesComponent); +export const routes: SecuritySubPluginRoutes = [ + { + path: TIMELINES_PATH, + render: TimelinesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 27d89130941bd..f9bea764c5987 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -30,9 +30,11 @@ import { MlPluginSetup, MlPluginStart } from '../../ml/public'; import { Detections } from './detections'; import { Cases } from './cases'; +import { Exceptions } from './exceptions'; import { Hosts } from './hosts'; import { Network } from './network'; import { Overview } from './overview'; +import { Rules } from './rules'; import { Timelines } from './timelines'; import { Management } from './management'; import { LicensingPluginStart, LicensingPluginSetup } from '../../licensing/public'; @@ -83,7 +85,9 @@ export interface AppObservableLibs { export type InspectResponse = Inspect & { response: string[] }; export interface SubPlugins { - detections: Detections; + alerts: Detections; + rules: Rules; + exceptions: Exceptions; cases: Cases; hosts: Hosts; network: Network; @@ -91,3 +95,16 @@ export interface SubPlugins { timelines: Timelines; management: Management; } + +// TODO: find a better way to defined these types +export interface StartedSubPlugins { + alerts: ReturnType<Detections['start']>; + rules: ReturnType<Rules['start']>; + exceptions: ReturnType<Exceptions['start']>; + cases: ReturnType<Cases['start']>; + hosts: ReturnType<Hosts['start']>; + network: ReturnType<Network['start']>; + overview: ReturnType<Overview['start']>; + timelines: ReturnType<Timelines['start']>; + management: ReturnType<Management['start']>; +} diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 4bcbcb71d048c..9f8b1923ff5b8 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -65,7 +65,6 @@ import { initUiSettings } from './ui_settings'; import { APP_ID, SERVER_APP_ID, - SecurityPageName, SIGNALS_ID, NOTIFICATIONS_ID, REFERENCE_RULE_ALERT_TYPE_ID, @@ -125,24 +124,6 @@ export interface PluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PluginStart {} - -const casesSubPlugin = `${APP_ID}:${SecurityPageName.case}`; - -/** - * Don't include cases here so that the sub feature can govern whether Cases is enabled in the navigation - */ -const securitySubPluginsNoCases = [ - APP_ID, - `${APP_ID}:${SecurityPageName.overview}`, - `${APP_ID}:${SecurityPageName.detections}`, - `${APP_ID}:${SecurityPageName.hosts}`, - `${APP_ID}:${SecurityPageName.network}`, - `${APP_ID}:${SecurityPageName.timelines}`, - `${APP_ID}:${SecurityPageName.administration}`, -]; - -const allSecuritySubPlugins = [...securitySubPluginsNoCases, casesSubPlugin]; - export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> { private readonly logger: Logger; private readonly config: ConfigType; @@ -308,7 +289,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S }), order: 1100, category: DEFAULT_APP_CATEGORIES.security, - app: [...allSecuritySubPlugins, 'kibana'], + app: [APP_ID, 'kibana'], catalogue: ['securitySolution'], management: { insightsAndAlerting: ['triggersActions'], @@ -323,9 +304,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S groupType: 'mutually_exclusive', privileges: [ { - // if the user is granted access to the cases feature than the global nav will show the cases - // sub plugin within the security solution navigation - app: [casesSubPlugin], id: 'cases_all', includeIn: 'all', name: 'All', @@ -341,7 +319,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S }, }, { - app: [casesSubPlugin], id: 'cases_read', includeIn: 'read', name: 'Read', @@ -363,7 +340,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S ], privileges: { all: { - app: [...securitySubPluginsNoCases, 'kibana'], + app: [APP_ID, 'kibana'], catalogue: ['securitySolution'], api: ['securitySolution', 'lists-all', 'lists-read'], savedObject: { @@ -384,7 +361,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S ui: ['show', 'crud'], }, read: { - app: [...securitySubPluginsNoCases, 'kibana'], + app: [APP_ID, 'kibana'], catalogue: ['securitySolution'], api: ['securitySolution', 'lists-read'], savedObject: { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7ffd5d8f20ffd..db6ec3ad7dd5b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20535,7 +20535,6 @@ "xpack.securitySolution.navigation.administration": "管理", "xpack.securitySolution.navigation.alerts": "アラート", "xpack.securitySolution.navigation.case": "ケース", - "xpack.securitySolution.navigation.detectionEngine": "検出", "xpack.securitySolution.navigation.hosts": "ホスト", "xpack.securitySolution.navigation.network": "ネットワーク", "xpack.securitySolution.navigation.overview": "概要", @@ -20855,8 +20854,6 @@ "xpack.securitySolution.search.cases": "ケース", "xpack.securitySolution.search.cases.configure": "ケースを構成", "xpack.securitySolution.search.cases.create": "新規ケースを作成", - "xpack.securitySolution.search.detections": "検出", - "xpack.securitySolution.search.detections.manage": "ルールの管理", "xpack.securitySolution.search.hosts": "ホスト", "xpack.securitySolution.search.hosts.anomalies": "異常", "xpack.securitySolution.search.hosts.authentications": "認証", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4a964fc5e2fd0..790f41209bd28 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20847,7 +20847,6 @@ "xpack.securitySolution.navigation.administration": "管理", "xpack.securitySolution.navigation.alerts": "告警", "xpack.securitySolution.navigation.case": "案例", - "xpack.securitySolution.navigation.detectionEngine": "检测", "xpack.securitySolution.navigation.hosts": "主机", "xpack.securitySolution.navigation.network": "网络", "xpack.securitySolution.navigation.overview": "概览", @@ -21187,8 +21186,6 @@ "xpack.securitySolution.search.cases": "案例", "xpack.securitySolution.search.cases.configure": "配置案例", "xpack.securitySolution.search.cases.create": "创建新案例", - "xpack.securitySolution.search.detections": "检测", - "xpack.securitySolution.search.detections.manage": "管理规则", "xpack.securitySolution.search.hosts": "主机", "xpack.securitySolution.search.hosts.anomalies": "异常", "xpack.securitySolution.search.hosts.authentications": "身份验证", From 0f79872219058e4e54e069499a4059f7641b3675 Mon Sep 17 00:00:00 2001 From: Pete Harverson <peteharverson@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:36:41 +0100 Subject: [PATCH 61/74] [ML] Sets max height and preserve whitespace in categorization wizard examples (#103637) * [ML] Sets max height and preserve whitespace in categorization wizard examples * [ML] Edit following review --- .../categorization_view/field_examples.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx index 954009c59abd5..01eee1c9cc37b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiBasicTable, EuiText } from '@elastic/eui'; +import { EuiBasicTable, EuiCodeBlock } from '@elastic/eui'; import { CategoryFieldExample } from '../../../../../../../../../common/types/categories'; interface Props { @@ -31,9 +31,14 @@ export const FieldExamples: FC<Props> = ({ fieldExamples }) => { } ), render: (example: any) => ( - <EuiText size="s"> - <code>{example}</code> - </EuiText> + <EuiCodeBlock + fontSize="s" + paddingSize="none" + transparentBackground + style={{ maxHeight: '200px' }} // Don't use overflowHeight as don't want to show the fullscreen button + > + {example} + </EuiCodeBlock> ), }, ]; From 480ccd5c986c8d084429ffaedb5b776319383924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= <davidsansol92@gmail.com> Date: Tue, 29 Jun 2021 15:48:18 +0200 Subject: [PATCH 62/74] [Security solution][Endpoint] Endpoint list columns are too compressed after new side bar (#103487) * Adjusts columns width after adding new side nav bar * Resize columns width --- .../public/management/pages/endpoint_hosts/view/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 7a553cfa8a32a..0ee345431055b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -251,6 +251,7 @@ export const EndpointList = () => { return [ { field: 'metadata', + width: '15%', name: i18n.translate('xpack.securitySolution.endpoint.list.hostname', { defaultMessage: 'Hostname', }), @@ -370,6 +371,7 @@ export const EndpointList = () => { }, { field: 'metadata.host.ip', + width: '12%', name: i18n.translate('xpack.securitySolution.endpoint.list.ip', { defaultMessage: 'IP Address', }), @@ -388,7 +390,7 @@ export const EndpointList = () => { }, { field: 'metadata.agent.version', - width: '5%', + width: '9%', name: i18n.translate('xpack.securitySolution.endpoint.list.endpointVersion', { defaultMessage: 'Version', }), @@ -396,6 +398,7 @@ export const EndpointList = () => { { field: 'metadata.@timestamp', name: lastActiveColumnName, + width: '9%', render(dateValue: HostInfo['metadata']['@timestamp']) { return ( <FormattedDate @@ -408,7 +411,7 @@ export const EndpointList = () => { }, { field: '', - width: '5%', + width: '8%', name: i18n.translate('xpack.securitySolution.endpoint.list.actions', { defaultMessage: 'Actions', }), From 75fa47cdcff8a5fa34f464f9f375fba948cbafd7 Mon Sep 17 00:00:00 2001 From: Tiago Costa <tiagoffcc@hotmail.com> Date: Tue, 29 Jun 2021 14:59:38 +0100 Subject: [PATCH 63/74] chore(NA): moving @kbn/test-subj-selector into bazel (#103562) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../monorepo-packages.asciidoc | 1 + package.json | 2 +- packages/BUILD.bazel | 1 + packages/kbn-test-subj-selector/BUILD.bazel | 48 +++++++++++++++++++ yarn.lock | 2 +- 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/kbn-test-subj-selector/BUILD.bazel diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 0ee4c09192896..7d708e17ae116 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -107,6 +107,7 @@ yarn kbn watch-bazel - @kbn/std - @kbn/storybook - @kbn/telemetry-utils +- @kbn/test-subj-selector - @kbn/tinymath - @kbn/ui-framework - @kbn/ui-shared-deps diff --git a/package.json b/package.json index 2e22a4e0ccf77..1111179fc816b 100644 --- a/package.json +++ b/package.json @@ -474,7 +474,7 @@ "@kbn/storybook": "link:bazel-bin/packages/kbn-storybook", "@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools", "@kbn/test": "link:packages/kbn-test", - "@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector", + "@kbn/test-subj-selector": "link:bazel-bin/packages/kbn-test-subj-selector", "@loaders.gl/polyfills": "^2.3.5", "@microsoft/api-documenter": "7.7.2", "@microsoft/api-extractor": "7.7.0", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 225a41a5fd8b6..38d3f28ec866b 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -52,6 +52,7 @@ filegroup( "//packages/kbn-std:build", "//packages/kbn-storybook:build", "//packages/kbn-telemetry-tools:build", + "//packages/kbn-test-subj-selector:build", "//packages/kbn-tinymath:build", "//packages/kbn-ui-framework:build", "//packages/kbn-ui-shared-deps:build", diff --git a/packages/kbn-test-subj-selector/BUILD.bazel b/packages/kbn-test-subj-selector/BUILD.bazel new file mode 100644 index 0000000000000..77751dc3e228a --- /dev/null +++ b/packages/kbn-test-subj-selector/BUILD.bazel @@ -0,0 +1,48 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-test-subj-selector" +PKG_REQUIRE_NAME = "@kbn/test-subj-selector" + +SOURCE_FILES = glob([ + "index.d.ts", + "index.js" +]) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +DEPS = [] + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES + [ + ":srcs", + ], + deps = DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/yarn.lock b/yarn.lock index ea968cf3631f2..21a7b445d4371 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2785,7 +2785,7 @@ version "0.0.0" uid "" -"@kbn/test-subj-selector@link:packages/kbn-test-subj-selector": +"@kbn/test-subj-selector@link:bazel-bin/packages/kbn-test-subj-selector": version "0.0.0" uid "" From a2f9e94fc0751e2fb79138145ea834242291cac1 Mon Sep 17 00:00:00 2001 From: Esteban Beltran <academo@users.noreply.github.com> Date: Tue, 29 Jun 2021 16:03:29 +0200 Subject: [PATCH 64/74] [Security Solution] Remove extra spaces below TakeActionDropdown alert flyout (#103618) --- .../timelines/components/side_panel/event_details/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index c4b19863ce7fc..58b991dafed67 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -171,8 +171,6 @@ const EventDetailsPanelComponent: React.FC<EventDetailsPanelProps> = ({ <TakeActionDropdown onChange={showHostIsolationPanel} agentId={agentId} /> </EuiFlexItem> </EuiFlexGroup> - <EuiSpacer size="l" /> - <EuiSpacer size="l" /> </EuiFlyoutFooter> )} </> From b2d76a6cd3f96ab152a8aaa81802a048df42051f Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@budzenski.me> Date: Tue, 29 Jun 2021 09:21:58 -0500 Subject: [PATCH 65/74] [build] Remove OSS builds (#100577) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/dev/build/README.md | 4 +- src/dev/build/args.test.ts | 7 -- src/dev/build/args.ts | 3 - src/dev/build/build_distributables.ts | 2 - src/dev/build/cli.ts | 2 - src/dev/build/lib/build.test.ts | 39 +++----- src/dev/build/lib/build.ts | 12 +-- src/dev/build/lib/runner.test.ts | 92 +------------------ src/dev/build/lib/runner.ts | 8 +- .../tasks/build_kibana_platform_plugins.ts | 2 +- src/dev/build/tasks/build_packages_task.ts | 2 - src/dev/build/tasks/create_archives_task.ts | 4 +- src/dev/build/tasks/install_chromium.js | 24 ++--- src/dev/build/tasks/license_file_task.ts | 16 +--- .../os_packages/create_os_package_tasks.ts | 39 ++++---- .../tasks/os_packages/docker_generator/run.ts | 12 +-- src/dev/build/tasks/os_packages/run_fpm.ts | 12 +-- test/scripts/jenkins_build_kibana.sh | 2 +- test/scripts/jenkins_build_load_testing.sh | 2 +- test/scripts/jenkins_xpack_baseline.sh | 2 +- 20 files changed, 61 insertions(+), 225 deletions(-) diff --git a/src/dev/build/README.md b/src/dev/build/README.md index f6e11af67da33..c499ef4a61083 100644 --- a/src/dev/build/README.md +++ b/src/dev/build/README.md @@ -12,8 +12,8 @@ node scripts/build --help # build a release version node scripts/build --release -# reuse already downloaded node executables, turn on debug logging, and only build the default distributable -node scripts/build --skip-node-download --debug --no-oss +# reuse already downloaded node executables, turn on debug logging +node scripts/build --skip-node-download --debug ``` # Fixing out of memory issues diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index e749af73241cf..af996321caa5a 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -27,7 +27,6 @@ it('build default and oss dist for current platform, without packages, by defaul Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -54,7 +53,6 @@ it('builds packages if --all-platforms is passed', () => { Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": true, @@ -81,7 +79,6 @@ it('limits packages if --rpm passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -108,7 +105,6 @@ it('limits packages if --deb passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, @@ -136,7 +132,6 @@ it('limits packages if --docker passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -171,7 +166,6 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -199,7 +193,6 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () => Object { "buildOptions": Object { "buildDefaultDist": true, - "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index bbfbd3e6f8813..54da6ac86d3f7 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -15,8 +15,6 @@ export function readCliArgs(argv: string[]) { const unknownFlags: string[] = []; const flags = getopts(argv, { boolean: [ - 'oss', - 'no-oss', 'skip-archives', 'skip-initialize', 'skip-generic-folders', @@ -94,7 +92,6 @@ export function readCliArgs(argv: string[]) { const buildOptions: BuildOptions = { isRelease: Boolean(flags.release), versionQualifier: flags['version-qualifier'], - buildOssDist: flags.oss !== false, buildDefaultDist: !flags.oss, initialize: !Boolean(flags['skip-initialize']), downloadFreshNode: !Boolean(flags['skip-node-download']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index f0403fac1e26b..ea543b19030ff 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -13,7 +13,6 @@ import * as Tasks from './tasks'; export interface BuildOptions { isRelease: boolean; - buildOssDist: boolean; buildDefaultDist: boolean; downloadFreshNode: boolean; initialize: boolean; @@ -38,7 +37,6 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions config, log, buildDefaultDist: options.buildDefaultDist, - buildOssDist: options.buildOssDist, }); /** diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index 3712e2230296a..c727c26d7dcd3 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -33,8 +33,6 @@ if (showHelp) { build the Kibana distributable options: - --oss {dim Only produce the OSS distributable of Kibana} - --no-oss {dim Only produce the default distributable of Kibana} --skip-archives {dim Don't produce tar/zip archives} --skip-os-packages {dim Don't produce rpm/deb/docker packages} --all-platforms {dim Produce archives for all platforms, not just this one} diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts index fe328329d7df0..f92a91e2b7221 100644 --- a/src/dev/build/lib/build.test.ts +++ b/src/dev/build/lib/build.test.ts @@ -43,40 +43,24 @@ beforeEach(() => { jest.clearAllMocks(); }); -const ossBuild = new Build(config, true); -const defaultBuild = new Build(config, false); - -describe('#isOss()', () => { - it('returns true for oss', () => { - expect(ossBuild.isOss()).toBe(true); - }); - - it('returns false for default build', () => { - expect(defaultBuild.isOss()).toBe(false); - }); -}); +const defaultBuild = new Build(config); describe('#getName()', () => { it('returns kibana for default build', () => { expect(defaultBuild.getName()).toBe('kibana'); }); - - it('returns kibana-oss for oss', () => { - expect(ossBuild.getName()).toBe('kibana-oss'); - }); }); describe('#getLogTag()', () => { it('returns string with build name in it', () => { expect(defaultBuild.getLogTag()).toContain(defaultBuild.getName()); - expect(ossBuild.getLogTag()).toContain(ossBuild.getName()); }); }); describe('#resolvePath()', () => { it('uses passed config to resolve a path relative to the repo', () => { - expect(ossBuild.resolvePath('bar')).toMatchInlineSnapshot( - `<absolute path>/build/kibana-oss/bar` + expect(defaultBuild.resolvePath('bar')).toMatchInlineSnapshot( + `<absolute path>/build/kibana/bar` ); }); @@ -89,28 +73,27 @@ describe('#resolvePath()', () => { describe('#resolvePathForPlatform()', () => { it('uses config.resolveFromRepo(), config.getBuildVersion(), and platform.getBuildName() to create path', () => { - expect(ossBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( - `<absolute path>/build/oss/kibana-8.0.0-linux-x86_64/foo/bar` + expect(defaultBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( + `<absolute path>/build/default/kibana-8.0.0-linux-x86_64/foo/bar` ); }); }); describe('#getPlatformArchivePath()', () => { it('creates correct path for different platforms', () => { - expect(ossBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-oss-8.0.0-linux-x86_64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-8.0.0-linux-x86_64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-oss-8.0.0-linux-aarch64.tar.gz` + expect(defaultBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-8.0.0-linux-aarch64.tar.gz` ); - expect(ossBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-oss-8.0.0-windows-x86_64.zip` + expect(defaultBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-8.0.0-windows-x86_64.zip` ); }); describe('#getRootDirectory()', () => { it('creates correct root directory name', () => { - expect(ossBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-oss-8.0.0"`); expect(defaultBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-8.0.0"`); }); }); diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts index f4ccb30994eef..c777ad18dc51f 100644 --- a/src/dev/build/lib/build.ts +++ b/src/dev/build/lib/build.ts @@ -12,14 +12,10 @@ import { Config } from './config'; import { Platform } from './platform'; export class Build { - private name = this.oss ? 'kibana-oss' : 'kibana'; - private logTag = this.oss ? chalk`{magenta [kibana-oss]}` : chalk`{cyan [ kibana ]}`; + private name = 'kibana'; + private logTag = chalk`{cyan [ kibana ]}`; - constructor(private config: Config, private oss: boolean) {} - - isOss() { - return !!this.oss; - } + constructor(private config: Config) {} resolvePath(...args: string[]) { return this.config.resolveFromRepo('build', this.name, ...args); @@ -28,7 +24,7 @@ export class Build { resolvePathForPlatform(platform: Platform, ...args: string[]) { return this.config.resolveFromRepo( 'build', - this.oss ? 'oss' : 'default', + 'default', `kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`, ...args ); diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts index 0e3c00d220ad9..581cbadeb5061 100644 --- a/src/dev/build/lib/runner.test.ts +++ b/src/dev/build/lib/runner.test.ts @@ -45,7 +45,7 @@ beforeEach(() => { jest.clearAllMocks(); }); -const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) => { +const setup = async (opts: { buildDefaultDist: boolean }) => { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, @@ -61,48 +61,10 @@ const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) return { config, run }; }; -describe('buildOssDist = true, buildDefaultDist = true', () => { +describe('default dist', () => { it('runs global task once, passing config and log', async () => { const { config, run } = await setup({ buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build), expect.any(Build)]); - }); - - it('calls local tasks twice, passing each build', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(2); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - }); -}); - -describe('just default dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: true, - buildOssDist: false, }); const mock = jest.fn(); @@ -120,49 +82,6 @@ describe('just default dist', () => { it('calls local tasks once, passing the default build', async () => { const { config, run } = await setup({ buildDefaultDist: true, - buildOssDist: false, - }); - - const mock = jest.fn(); - - await run({ - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (build.isOss()) { - throw new Error('expected build to be the default dist, not the oss dist'); - } - }); -}); - -describe('just oss dist', () => { - it('runs global task once, passing config and log', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, - }); - - const mock = jest.fn(); - - await run({ - global: true, - description: 'foo', - run: mock, - }); - - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build)]); - }); - - it('calls local tasks once, passing the oss build', async () => { - const { config, run } = await setup({ - buildDefaultDist: false, - buildOssDist: true, }); const mock = jest.fn(); @@ -174,11 +93,6 @@ describe('just oss dist', () => { expect(mock).toHaveBeenCalledTimes(1); expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); - const [args] = mock.mock.calls; - const [, , build] = args; - if (!build.isOss()) { - throw new Error('expected build to be the oss dist, not the default dist'); - } }); }); @@ -186,7 +100,6 @@ describe('task rejection', () => { it('rejects, logs error, and marks error logged', async () => { const { run } = await setup({ buildDefaultDist: true, - buildOssDist: false, }); const error = new Error('FOO'); @@ -215,7 +128,6 @@ describe('task rejection', () => { it('just rethrows errors that have already been logged', async () => { const { run } = await setup({ buildDefaultDist: true, - buildOssDist: false, }); const error = markErrorLogged(new Error('FOO')); diff --git a/src/dev/build/lib/runner.ts b/src/dev/build/lib/runner.ts index 015de6fe7e9ef..eccde9160f8a3 100644 --- a/src/dev/build/lib/runner.ts +++ b/src/dev/build/lib/runner.ts @@ -16,7 +16,6 @@ import { Config } from './config'; interface Options { config: Config; log: ToolingLog; - buildOssDist: boolean; buildDefaultDist: boolean; } @@ -32,7 +31,7 @@ export interface Task { run(config: Config, log: ToolingLog, build: Build): Promise<void>; } -export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Options) { +export function createRunner({ config, log, buildDefaultDist }: Options) { async function execTask(desc: string, task: Task | GlobalTask, lastArg: any) { log.info(desc); log.indent(4); @@ -64,10 +63,7 @@ export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Op const builds: Build[] = []; if (buildDefaultDist) { - builds.push(new Build(config, false)); - } - if (buildOssDist) { - builds.push(new Build(config, true)); + builds.push(new Build(config)); } /** diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index edff77d458f0f..7d8690df6f3ce 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -27,7 +27,7 @@ export const BuildKibanaPlatformPlugins: Task = { repoRoot: REPO_ROOT, outputRoot: build.resolvePath(), cache: false, - oss: build.isOss(), + oss: false, examples: false, watch: false, dist: true, diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts index e6305b3761a4f..808903661a595 100644 --- a/src/dev/build/tasks/build_packages_task.ts +++ b/src/dev/build/tasks/build_packages_task.ts @@ -63,7 +63,6 @@ export const BuildBazelPackages: Task = { await buildBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; @@ -75,7 +74,6 @@ export const BuildPackages: Task = { await buildNonBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), - onlyOSS: build.isOss(), }); }, }; diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts index e2d3ff7149c4c..37c4becae76a8 100644 --- a/src/dev/build/tasks/create_archives_task.ts +++ b/src/dev/build/tasks/create_archives_task.ts @@ -77,14 +77,14 @@ export const CreateArchives: Task = { const metrics: CiStatsMetric[] = []; for (const { format, path, fileCount } of archives) { metrics.push({ - group: `${build.isOss() ? 'oss ' : ''}distributable size`, + group: `distributable size`, id: format, value: (await asyncStat(path)).size, }); metrics.push({ group: 'distributable file count', - id: build.isOss() ? 'oss' : 'default', + id: 'default', value: fileCount, }); } diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index b21a8484fa710..37abcbad4466e 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -15,21 +15,17 @@ export const InstallChromium = { description: 'Installing Chromium', async run(config, log, build) { - if (build.isOss()) { - return; - } else { - for (const platform of config.getNodePlatforms()) { - log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); + for (const platform of config.getNodePlatforms()) { + log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); - const { binaryPath$ } = installBrowser( - // TODO: https://github.com/elastic/kibana/issues/72496 - log, - build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), - platform.getName(), - platform.getArchitecture() - ); - await binaryPath$.pipe(first()).toPromise(); - } + const { binaryPath$ } = installBrowser( + // TODO: https://github.com/elastic/kibana/issues/72496 + log, + build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), + platform.getName(), + platform.getArchitecture() + ); + await binaryPath$.pipe(first()).toPromise(); } }, }; diff --git a/src/dev/build/tasks/license_file_task.ts b/src/dev/build/tasks/license_file_task.ts index 7e5ed8da0a27a..ff33707476576 100644 --- a/src/dev/build/tasks/license_file_task.ts +++ b/src/dev/build/tasks/license_file_task.ts @@ -8,23 +8,13 @@ import { write, read, Task } from '../lib'; -const LICENSE_SEPARATOR = `\n------------------------------------------------------------------------\n\n`; - export const UpdateLicenseFile: Task = { description: 'Updating LICENSE.txt file', async run(config, log, build) { const elasticLicense = await read(config.resolveFromRepo('licenses/ELASTIC-LICENSE-2.0.txt')); - if (build.isOss()) { - const ssplLicense = await read(config.resolveFromRepo('licenses/SSPL-LICENSE.txt')); - log.info('Copying dual-license to LICENSE.txt'); - await write( - build.resolvePath('LICENSE.txt'), - ssplLicense + LICENSE_SEPARATOR + elasticLicense - ); - } else { - log.info('Copying Elastic license to LICENSE.txt'); - await write(build.resolvePath('LICENSE.txt'), elasticLicense); - } + + log.info('Copying Elastic license to LICENSE.txt'); + await write(build.resolvePath('LICENSE.txt'), elasticLicense); }, }; diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 2ae882000cae0..99d0e1998e78a 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -73,15 +73,12 @@ export const CreateDockerUBI: Task = { description: 'Creating Docker UBI image', async run(config, log, build) { - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - architecture: 'x64', - context: false, - ubi: true, - image: true, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + architecture: 'x64', + context: false, + ubi: true, + image: true, + }); }, }; @@ -95,19 +92,15 @@ export const CreateDockerContexts: Task = { dockerBuildDate, }); - if (!build.isOss()) { - await runDockerGenerator(config, log, build, { - ubi: true, - context: true, - image: false, - dockerBuildDate, - }); - await runDockerGenerator(config, log, build, { - ironbank: true, - context: true, - image: false, - dockerBuildDate, - }); - } + await runDockerGenerator(config, log, build, { + ubi: true, + context: true, + image: false, + }); + await runDockerGenerator(config, log, build, { + ironbank: true, + context: true, + image: false, + }); }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index c72112b7b6b03..97fd740409741 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -43,24 +43,18 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.ubi) imageFlavor += `-${ubiVersionTag}`; if (flags.ironbank) imageFlavor += '-ironbank'; - if (build.isOss()) imageFlavor += '-oss'; // General docker var config - const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; + const license = 'Elastic License'; const imageTag = 'docker.elastic.co/kibana/kibana'; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; - const artifactFlavor = build.isOss() ? '-oss' : ''; - const artifactPrefix = `kibana${artifactFlavor}-${version}-linux`; + const artifactPrefix = `kibana-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); // That would produce oss, default and default-ubi7 - const dockerBuildDir = config.resolveFromRepo( - 'build', - 'kibana-docker', - build.isOss() ? `oss` : `default${imageFlavor}` - ); + const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index 933b3e411b286..b732e4c80ea37 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -28,11 +28,7 @@ export async function runFpm( const fromBuild = (...paths: string[]) => build.resolvePathForPlatform(linux, ...paths); const pickLicense = () => { - if (build.isOss()) { - return type === 'rpm' ? 'ASL 2.0' : 'ASL-2.0'; - } else { - return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; - } + return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; }; const envFolder = type === 'rpm' ? 'sysconfig' : 'default'; @@ -57,7 +53,7 @@ export async function runFpm( // general info about the package '--name', - build.isOss() ? 'kibana-oss' : 'kibana', + 'kibana', '--description', 'Explore and visualize your Elasticsearch data', '--version', @@ -71,10 +67,6 @@ export async function runFpm( '--license', pickLicense(), - // prevent installing kibana if installing kibana-oss and vice versa - '--conflicts', - build.isOss() ? 'kibana' : 'kibana-oss', - // define install/uninstall scripts '--after-install', resolve(__dirname, 'package_scripts/post_install.sh'), diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 198723908cf48..a9edd3ed2a701 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -33,7 +33,7 @@ node x-pack/scripts/functional_tests --assert-none-excluded \ # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug --no-oss + node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index 5571eee4f28ed..d7c7bda83c9ef 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -60,7 +60,7 @@ export KBN_NP_PLUGINS_BUILT=true echo " -> Building and extracting default Kibana distributable for use in functional tests" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index 8d5624949505a..c68c0f40902f2 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -5,7 +5,7 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting default Kibana distributable" cd "$KIBANA_DIR" -node scripts/build --debug --no-oss +node scripts/build --debug echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ From 6c3019dfe2de9a1c720b999614e786474f8f3a28 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Tue, 29 Jun 2021 17:36:04 +0300 Subject: [PATCH 66/74] [Discover] Replace doc viewer table with EuiInMemoryTable (#102149) * [Discover] replace legacy table with euiInMemoryTable * [Discover] update styles, add badge * fix font in badge and adjust line height * add tooltip * [Discover] update unit tests, return actions column to left side * [Discover] update field name test snapshot * [Discover] update wording * [Discover] handle pagination, return formatting value styles * [Discover] fix failing stylelint error * [Discover] return responsive prop, update classes * [Discover] update test and meet formatting rules * improve table view on medium * remove extra file * [Discover] fix unit test * [Discover] align top vertically field name and action cells, disable table responsive design * [Discover] adjust styles for cross browser compatibility * [Discover] remove pagination optimize styles, update test * [Discover] fix eslint * [Discover] clean up styles * [Discover] fix single doc view * [Discover] add check lack of multifieldBadge Co-authored-by: Andrea Del Rio <delrio.andre@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/doc_viewer/doc_viewer.scss | 89 +++---- .../__snapshots__/field_name.test.tsx.snap | 84 ++++--- .../components/field_name/field_name.scss | 12 + .../components/field_name/field_name.tsx | 68 ++++-- .../components/table/table.test.tsx | 75 ++++-- .../application/components/table/table.tsx | 230 ++++++++---------- .../components/table/table_cell_actions.tsx | 60 +++++ .../components/table/table_cell_value.tsx | 66 +++++ .../components/table/table_columns.tsx | 92 +++++++ .../components/table/table_row.tsx | 112 --------- src/plugins/discover/public/plugin.tsx | 5 +- 11 files changed, 508 insertions(+), 385 deletions(-) create mode 100644 src/plugins/discover/public/application/components/field_name/field_name.scss create mode 100644 src/plugins/discover/public/application/components/table/table_cell_actions.tsx create mode 100644 src/plugins/discover/public/application/components/table/table_cell_value.tsx create mode 100644 src/plugins/discover/public/application/components/table/table_columns.tsx delete mode 100644 src/plugins/discover/public/application/components/table/table_row.tsx diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss index f5a4180207c33..12d56d564b855 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss @@ -1,82 +1,57 @@ -.kbnDocViewerTable { - @include euiBreakpoint('xs', 's','m') { - table-layout: fixed; - } -} - .kbnDocViewer { - pre, - .kbnDocViewer__value { - display: inline-block; - word-break: break-all; - word-wrap: break-word; - white-space: pre-wrap; - color: $euiColorFullShade; + .euiTableRowCell { vertical-align: top; - padding-top: $euiSizeXS * .5; - } - .kbnDocViewer__field { - padding-top: $euiSizeS; - } - - .kbnDocViewer__multifield_row:hover td { - background-color: transparent; - } - - .kbnDocViewer__multifield_title { - font-family: $euiFontFamily; } - .dscFieldName { - color: $euiColorDarkShade; + tr:first-child th { + border-bottom-color: transparent; } +} - td, - pre { - font-family: $euiCodeFontFamily; - } +.kbnDocViewer__tableRow { + font-size: $euiFontSizeXS; + font-family: $euiCodeFontFamily; - tr:first-child td { - border-top-color: transparent; + .kbnDocViewer__buttons { + // Show all icons if one is focused, + &:focus-within { + .kbnDocViewer__actionButton { + opacity: 1; + } + } } - tr:hover { + &:hover { .kbnDocViewer__actionButton { opacity: 1; } } -} -.kbnDocViewer__buttons, -.kbnDocViewer__field { - white-space: nowrap; -} -.kbnDocViewer__buttons { - width: 108px; + .kbnDocViewer__actionButton { + @include euiBreakpoint('m', 'l', 'xl') { + opacity: 0; + } - // Show all icons if one is focused, - &:focus-within { - .kbnDocViewer__actionButton { + &:focus { opacity: 1; } } } -.kbnDocViewer__field { - width: $euiSize * 10; - @include euiBreakpoint('xs', 's', 'm') { - width: $euiSize * 6; - } +.kbnDocViewer__tableActionsCell, +.kbnDocViewer__tableFieldNameCell { + align-items: flex-start; + padding: $euiSizeXS; } -.kbnDocViewer__actionButton { - @include euiBreakpoint('m', 'l', 'xl') { - opacity: 0; - } - - &:focus { - opacity: 1; - } +.kbnDocViewer__value { + display: inline-block; + word-break: break-all; + word-wrap: break-word; + white-space: pre-wrap; + line-height: $euiLineHeight; + color: $euiColorFullShade; + vertical-align: top; } .kbnDocViewer__warning { diff --git a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap index 6b5e45f8a0448..284c77c96db78 100644 --- a/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap +++ b/src/plugins/discover/public/application/components/field_name/__snapshots__/field_name.test.tsx.snap @@ -1,11 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FieldName renders a geo field 1`] = ` -<div - class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow" -> +Array [ <div - class="euiFlexItem euiFlexItem--flexGrowZero" + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldIcon" > <span class="euiToken euiToken--euiColorVis5 euiToken--square euiToken--light euiToken--small kbnFieldIcon" @@ -16,27 +14,29 @@ exports[`FieldName renders a geo field 1`] = ` title="Geo point field" /> </span> - </div> + </div>, <div - class="euiFlexItem eui-textTruncate" + class="euiFlexGroup euiFlexGroup--alignItemsFlexStart euiFlexGroup--directionRow euiFlexGroup--wrap" > - <span - class="euiToolTipAnchor eui-textTruncate" + <div + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldName eui-textBreakAll" > - <span> - test.test.test + <span + class="euiToolTipAnchor eui-textBreakAll" + > + <span> + test.test.test + </span> </span> - </span> - </div> -</div> + </div> + </div>, +] `; exports[`FieldName renders a number field by providing a field record 1`] = ` -<div - class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow" -> +Array [ <div - class="euiFlexItem euiFlexItem--flexGrowZero" + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldIcon" > <span class="euiToken euiToken--euiColorVis0 euiToken--square euiToken--light euiToken--small kbnFieldIcon" @@ -47,27 +47,29 @@ exports[`FieldName renders a number field by providing a field record 1`] = ` title="Number field" /> </span> - </div> + </div>, <div - class="euiFlexItem eui-textTruncate" + class="euiFlexGroup euiFlexGroup--alignItemsFlexStart euiFlexGroup--directionRow euiFlexGroup--wrap" > - <span - class="euiToolTipAnchor eui-textTruncate" + <div + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldName eui-textBreakAll" > - <span> - test.test.test + <span + class="euiToolTipAnchor eui-textBreakAll" + > + <span> + test.test.test + </span> </span> - </span> - </div> -</div> + </div> + </div>, +] `; exports[`FieldName renders a string field by providing fieldType and fieldName 1`] = ` -<div - class="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow" -> +Array [ <div - class="euiFlexItem euiFlexItem--flexGrowZero" + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldIcon" > <span class="euiToken euiToken--euiColorVis1 euiToken--square euiToken--light euiToken--small kbnFieldIcon" @@ -78,17 +80,21 @@ exports[`FieldName renders a string field by providing fieldType and fieldName 1 title="String field" /> </span> - </div> + </div>, <div - class="euiFlexItem eui-textTruncate" + class="euiFlexGroup euiFlexGroup--alignItemsFlexStart euiFlexGroup--directionRow euiFlexGroup--wrap" > - <span - class="euiToolTipAnchor eui-textTruncate" + <div + class="euiFlexItem euiFlexItem--flexGrowZero kbnDocViewer__fieldName eui-textBreakAll" > - <span> - test + <span + class="euiToolTipAnchor eui-textBreakAll" + > + <span> + test + </span> </span> - </span> - </div> -</div> + </div> + </div>, +] `; diff --git a/src/plugins/discover/public/application/components/field_name/field_name.scss b/src/plugins/discover/public/application/components/field_name/field_name.scss new file mode 100644 index 0000000000000..edc4ea270d755 --- /dev/null +++ b/src/plugins/discover/public/application/components/field_name/field_name.scss @@ -0,0 +1,12 @@ +.kbnDocViewer__fieldIcon { + padding-top: $euiSizeXS * 1.5; +} + +.kbnDocViewer__fieldName { + line-height: $euiLineHeight; + padding: $euiSizeXS; +} + +.kbnDocViewer__multiFieldBadge { + @include euiFont; +} diff --git a/src/plugins/discover/public/application/components/field_name/field_name.tsx b/src/plugins/discover/public/application/components/field_name/field_name.tsx index d89ce7eb051ee..e8b6765b738a5 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.tsx @@ -6,21 +6,23 @@ * Side Public License, v 1. */ -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import './field_name.scss'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { FieldIcon, FieldIconProps } from '../../../../../kibana_react/public'; import { getFieldTypeName } from './field_type_name'; -import { FieldMapping } from '../../doc_views/doc_views_types'; +import { IndexPatternField } from '../../../../../data/public'; // properties fieldType and fieldName are provided in kbn_doc_view // this should be changed when both components are deangularized interface Props { fieldName: string; fieldType: string; - fieldMapping?: FieldMapping; + fieldMapping?: IndexPatternField; fieldIconProps?: Omit<FieldIconProps, 'type'>; scripted?: boolean; - className?: string; } export function FieldName({ @@ -28,29 +30,57 @@ export function FieldName({ fieldMapping, fieldType, fieldIconProps, - className, scripted = false, }: Props) { const typeName = getFieldTypeName(fieldType); const displayName = fieldMapping && fieldMapping.displayName ? fieldMapping.displayName : fieldName; const tooltip = displayName !== fieldName ? `${fieldName} (${displayName})` : fieldName; + const isMultiField = !!fieldMapping?.spec?.subType?.multi; return ( - <EuiFlexGroup className={className} alignItems="center" gutterSize="s" responsive={false}> - <EuiFlexItem grow={false}> + <Fragment> + <EuiFlexItem grow={false} className="kbnDocViewer__fieldIcon"> <FieldIcon type={fieldType} label={typeName} scripted={scripted} {...fieldIconProps} /> </EuiFlexItem> - <EuiFlexItem className="eui-textTruncate"> - <EuiToolTip - position="top" - content={tooltip} - delay="long" - anchorClassName="eui-textTruncate" - > - <span>{displayName}</span> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> + + <EuiFlexGroup wrap={true} gutterSize="none" responsive={false} alignItems="flexStart"> + <EuiFlexItem className="kbnDocViewer__fieldName eui-textBreakAll" grow={false}> + <EuiToolTip + position="top" + content={tooltip} + delay="long" + anchorClassName="eui-textBreakAll" + > + <span>{displayName}</span> + </EuiToolTip> + </EuiFlexItem> + + {isMultiField && ( + <EuiToolTip + position="top" + delay="long" + content={i18n.translate( + 'discover.fieldChooser.discoverField.multiFieldTooltipContent', + { + defaultMessage: 'Multi-fields can have multiple values per field', + } + )} + > + <EuiBadge + title="" + className="kbnDocViewer__multiFieldBadge" + color="default" + data-test-subj={`tableDocViewRow-${fieldName}-multifieldBadge`} + > + <FormattedMessage + id="discover.fieldChooser.discoverField.multiField" + defaultMessage="multi-field" + /> + </EuiBadge> + </EuiToolTip> + )} + </EuiFlexGroup> + </Fragment> ); } diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index bf27f1871cf24..a38a9e41aa242 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -9,7 +9,8 @@ import React from 'react'; import { mount } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { DocViewTable } from './table'; +import { I18nProvider } from '@kbn/i18n/react'; +import { DocViewerTable, DocViewerTableProps } from './table'; import { indexPatterns, IndexPattern } from '../../../../../data/public'; import { ElasticSearchHit } from '../../doc_views/doc_views_types'; @@ -23,7 +24,7 @@ import { getServices } from '../../../kibana_services'; uiSettings: { get: (key: string) => { if (key === 'discover:showMultiFields') { - return false; + return true; } }, }, @@ -75,6 +76,14 @@ indexPattern.fields.getByName = (name: string) => { indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); +const mountComponent = (props: DocViewerTableProps) => { + return mount( + <I18nProvider> + <DocViewerTable {...props} /> + </I18nProvider> + ); +}; + describe('DocViewTable at Discover', () => { // At Discover's main view, all buttons are rendered // check for existence of action buttons and warnings @@ -114,7 +123,7 @@ describe('DocViewTable at Discover', () => { onAddColumn: jest.fn(), onRemoveColumn: jest.fn(), }; - const component = mount(<DocViewTable {...props} />); + const component = mountComponent(props); [ { _property: '_index', @@ -210,7 +219,7 @@ describe('DocViewTable at Discover Context', () => { filter: jest.fn(), }; - const component = mount(<DocViewTable {...props} />); + const component = mountComponent(props); it(`renders no toggleColumnButton`, () => { const foundLength = findTestSubject(component, 'toggleColumnButtons').length; @@ -253,7 +262,7 @@ describe('DocViewTable at Discover Doc', () => { hit, indexPattern, }; - const component = mount(<DocViewTable {...props} />); + const component = mountComponent(props); const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length; it(`renders no action buttons`, () => { @@ -381,20 +390,10 @@ describe('DocViewTable at Discover Doc with Fields API', () => { onAddColumn: jest.fn(), onRemoveColumn: jest.fn(), }; + it('renders multifield rows if showMultiFields flag is set', () => { - (getServices as jest.Mock).mockImplementationOnce(() => ({ - uiSettings: { - get: (key: string) => { - return key === 'discover:showMultiFields'; - }, - }, - })); - const component = mount(<DocViewTable {...props} />); - const categoryMultifieldRow = findTestSubject( - component, - 'tableDocViewRow-multifieldsTitle-category' - ); - expect(categoryMultifieldRow.length).toBe(1); + const component = mountComponent(props); + const categoryKeywordRow = findTestSubject(component, 'tableDocViewRow-category.keyword'); expect(categoryKeywordRow.length).toBe(1); @@ -404,23 +403,51 @@ describe('DocViewTable at Discover Doc with Fields API', () => { expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname').length).toBe( 1 ); + + expect( + findTestSubject(component, 'tableDocViewRow-category.keyword-multifieldBadge').length + ).toBe(1); + + expect( + findTestSubject(component, 'tableDocViewRow-customer_first_name.keyword-multifieldBadge') + .length + ).toBe(1); + + expect( + findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname-multifieldBadge') + .length + ).toBe(1); }); it('does not render multifield rows if showMultiFields flag is not set', () => { - const component = mount(<DocViewTable {...props} />); - const categoryMultifieldRow = findTestSubject( - component, - 'tableDocViewRow-multifieldsTitle-category' - ); - expect(categoryMultifieldRow.length).toBe(0); + (getServices as jest.Mock).mockImplementationOnce(() => ({ + uiSettings: { + get: (key: string) => { + return key === 'discover:showMultiFields' && false; + }, + }, + })); + const component = mountComponent(props); + const categoryKeywordRow = findTestSubject(component, 'tableDocViewRow-category.keyword'); expect(categoryKeywordRow.length).toBe(0); expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.keyword').length).toBe( 0 ); + expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname').length).toBe( 0 ); + + expect( + findTestSubject(component, 'tableDocViewRow-customer_first_name.keyword-multifieldBadge') + .length + ).toBe(0); + + expect( + findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname-multifieldBadge') + .length + ).toBe(0); }); }); diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index 4d24fe15519a1..065b146105a65 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -6,60 +6,67 @@ * Side Public License, v 1. */ -import React, { useState, useEffect, useCallback } from 'react'; -import { i18n } from '@kbn/i18n'; -import { DocViewTableRow } from './table_row'; -import { trimAngularSpan } from './table_helper'; -import { isNestedFieldParent } from '../../apps/main/utils/nested_fields'; -import { DocViewRenderProps } from '../../doc_views/doc_views_types'; -import { getServices } from '../../../kibana_services'; +import React, { useCallback, useMemo } from 'react'; +import { EuiInMemoryTable } from '@elastic/eui'; +import { IndexPattern, IndexPatternField } from '../../../../../data/public'; import { SHOW_MULTIFIELDS } from '../../../../common'; +import { getServices } from '../../../kibana_services'; +import { isNestedFieldParent } from '../../apps/main/utils/nested_fields'; +import { + DocViewFilterFn, + ElasticSearchHit, + DocViewRenderProps, +} from '../../doc_views/doc_views_types'; +import { ACTIONS_COLUMN, MAIN_COLUMNS } from './table_columns'; -const COLLAPSE_LINE_LENGTH = 350; +export interface DocViewerTableProps { + columns?: string[]; + filter?: DocViewFilterFn; + hit: ElasticSearchHit; + indexPattern?: IndexPattern; + onAddColumn?: (columnName: string) => void; + onRemoveColumn?: (columnName: string) => void; +} -export function DocViewTable({ +export interface FieldRecord { + action: { + isActive: boolean; + onFilter?: DocViewFilterFn; + onToggleColumn: (field: string) => void; + flattenedField: unknown; + }; + field: { + fieldName: string; + fieldType: string; + fieldMapping: IndexPatternField | undefined; + scripted: boolean; + }; + value: { + formattedField: unknown; + }; +} + +export const DocViewerTable = ({ + columns, hit, indexPattern, filter, - columns, onAddColumn, onRemoveColumn, -}: DocViewRenderProps) { - const [fieldRowOpen, setFieldRowOpen] = useState({} as Record<string, boolean>); - const [multiFields, setMultiFields] = useState({} as Record<string, string[]>); - const [fieldsWithParents, setFieldsWithParents] = useState([] as string[]); +}: DocViewRenderProps) => { const showMultiFields = getServices().uiSettings.get(SHOW_MULTIFIELDS); - useEffect(() => { - if (!indexPattern) { - return; - } - const mapping = indexPattern.fields.getByName; - const flattened = indexPattern.flattenHit(hit); - const map: Record<string, string[]> = {}; - const arr: string[] = []; + const mapping = useCallback((name) => indexPattern?.fields.getByName(name), [ + indexPattern?.fields, + ]); - Object.keys(flattened).forEach((key) => { - const field = mapping(key); + const formattedHit = useMemo(() => indexPattern?.formatHit(hit, 'html'), [hit, indexPattern]); - if (field && field.spec?.subType?.multi?.parent) { - const parent = field.spec.subType.multi.parent; - if (!map[parent]) { - map[parent] = [] as string[]; - } - const value = map[parent]; - value.push(key); - map[parent] = value; - arr.push(key); - } - }); - if (showMultiFields) { - setMultiFields(map); - } - setFieldsWithParents(arr); - }, [indexPattern, hit, showMultiFields]); + const tableColumns = useMemo(() => { + return filter ? [ACTIONS_COLUMN, ...MAIN_COLUMNS] : MAIN_COLUMNS; + }, [filter]); - const toggleColumn = useCallback( + const onToggleColumn = useCallback( (field: string) => { if (!onRemoveColumn || !onAddColumn || !columns) { return; @@ -73,101 +80,62 @@ export function DocViewTable({ [onRemoveColumn, onAddColumn, columns] ); + const onSetRowProps = useCallback(({ field: { fieldName } }: FieldRecord) => { + return { + className: 'kbnDocViewer__tableRow', + 'data-test-subj': `tableDocViewRow-${fieldName}`, + }; + }, []); + if (!indexPattern) { return null; } - const mapping = indexPattern.fields.getByName; + const flattened = indexPattern.flattenHit(hit); - const formatted = indexPattern.formatHit(hit, 'html'); + const items: FieldRecord[] = Object.keys(flattened) + .filter((fieldName) => { + const fieldMapping = mapping(fieldName); + const isMultiField = !!fieldMapping?.spec?.subType?.multi; + return isMultiField ? showMultiFields : true; + }) + .sort((fieldA, fieldB) => { + const mappingA = mapping(fieldA); + const mappingB = mapping(fieldB); + const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName; + const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName; + return nameA.localeCompare(nameB); + }) + .map((fieldName) => { + const fieldMapping = mapping(fieldName); + const fieldType = isNestedFieldParent(fieldName, indexPattern) + ? 'nested' + : fieldMapping?.type; - function toggleValueCollapse(field: string) { - fieldRowOpen[field] = !fieldRowOpen[field]; - setFieldRowOpen({ ...fieldRowOpen }); - } + return { + action: { + onToggleColumn, + onFilter: filter, + isActive: !!columns?.includes(fieldName), + flattenedField: flattened[fieldName], + }, + field: { + fieldName, + fieldType: fieldType!, + scripted: Boolean(fieldMapping?.scripted), + fieldMapping, + }, + value: { formattedField: formattedHit[fieldName] }, + }; + }); return ( - <table className="table table-condensed kbnDocViewerTable"> - <tbody> - {Object.keys(flattened) - .filter((field) => { - return !fieldsWithParents.includes(field); - }) - .sort((fieldA, fieldB) => { - const mappingA = mapping(fieldA); - const mappingB = mapping(fieldB); - const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName; - const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName; - return nameA.localeCompare(nameB); - }) - .map((field) => { - const valueRaw = flattened[field]; - const value = trimAngularSpan(String(formatted[field])); - - const isCollapsible = value.length > COLLAPSE_LINE_LENGTH; - const isCollapsed = isCollapsible && !fieldRowOpen[field]; - const displayUnderscoreWarning = !mapping(field) && field.indexOf('_') === 0; - - const fieldType = isNestedFieldParent(field, indexPattern) - ? 'nested' - : indexPattern.fields.getByName(field)?.type; - return ( - <React.Fragment key={field}> - <DocViewTableRow - field={field} - fieldMapping={mapping(field)} - fieldType={String(fieldType)} - displayUnderscoreWarning={displayUnderscoreWarning} - isCollapsed={isCollapsed} - isCollapsible={isCollapsible} - isColumnActive={!!columns?.includes(field)} - onFilter={filter} - onToggleCollapse={() => toggleValueCollapse(field)} - onToggleColumn={() => toggleColumn(field)} - value={value} - valueRaw={valueRaw} - /> - {multiFields[field] ? ( - <tr - key={`tableDocViewRow-multifieldsTitle-${field}`} - className="kbnDocViewer__multifield_row" - data-test-subj={`tableDocViewRow-multifieldsTitle-${field}`} - > - <td className="kbnDocViewer__field"> </td> - <td className="kbnDocViewer__multifield_title" colSpan={2}> - <b> - {i18n.translate('discover.fieldChooser.discoverField.multiFields', { - defaultMessage: 'Multi fields', - })} - </b> - </td> - </tr> - ) : null} - {multiFields[field] - ? multiFields[field].map((multiField) => { - return ( - <DocViewTableRow - key={multiField} - fieldMapping={mapping(multiField)} - fieldType={String(fieldType)} - displayUnderscoreWarning={displayUnderscoreWarning} - isCollapsed={isCollapsed} - isCollapsible={isCollapsible} - isColumnActive={Array.isArray(columns) && columns.includes(multiField)} - onFilter={filter} - onToggleCollapse={() => { - toggleValueCollapse(multiField); - }} - onToggleColumn={() => toggleColumn(multiField)} - value={value} - valueRaw={valueRaw} - /> - ); - }) - : null} - </React.Fragment> - ); - })} - </tbody> - </table> + <EuiInMemoryTable + className="kbnDocViewer__table" + items={items} + columns={tableColumns} + rowProps={onSetRowProps} + pagination={false} + responsive={false} + /> ); -} +}; diff --git a/src/plugins/discover/public/application/components/table/table_cell_actions.tsx b/src/plugins/discover/public/application/components/table/table_cell_actions.tsx new file mode 100644 index 0000000000000..3344797b4d32a --- /dev/null +++ b/src/plugins/discover/public/application/components/table/table_cell_actions.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { DocViewTableRowBtnFilterRemove } from './table_row_btn_filter_remove'; +import { DocViewTableRowBtnFilterExists } from './table_row_btn_filter_exists'; +import { DocViewTableRowBtnToggleColumn } from './table_row_btn_toggle_column'; +import { DocViewTableRowBtnFilterAdd } from './table_row_btn_filter_add'; +import { IndexPatternField } from '../../../../../data/public'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; + +interface TableActionsProps { + isActive: boolean; + fieldName: string; + flattenedField: unknown; + fieldMapping: IndexPatternField | undefined; + onFilter: DocViewFilterFn; + onToggleColumn: (field: string) => void; +} + +export const TableActions = ({ + isActive, + fieldName, + fieldMapping, + flattenedField, + onToggleColumn, + onFilter, +}: TableActionsProps) => { + const toggleColumn = () => { + onToggleColumn(fieldName); + }; + + return ( + <div className="kbnDocViewer__buttons"> + <DocViewTableRowBtnFilterAdd + disabled={!fieldMapping || !fieldMapping.filterable} + onClick={() => onFilter(fieldMapping, flattenedField, '+')} + /> + <DocViewTableRowBtnFilterRemove + disabled={!fieldMapping || !fieldMapping.filterable} + onClick={() => onFilter(fieldMapping, flattenedField, '-')} + /> + <DocViewTableRowBtnToggleColumn + active={isActive} + fieldname={fieldName} + onClick={toggleColumn} + /> + <DocViewTableRowBtnFilterExists + disabled={!fieldMapping || !fieldMapping.filterable} + onClick={() => onFilter('_exists_', fieldName, '+')} + scripted={fieldMapping && fieldMapping.scripted} + /> + </div> + ); +}; diff --git a/src/plugins/discover/public/application/components/table/table_cell_value.tsx b/src/plugins/discover/public/application/components/table/table_cell_value.tsx new file mode 100644 index 0000000000000..de29068a82718 --- /dev/null +++ b/src/plugins/discover/public/application/components/table/table_cell_value.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import classNames from 'classnames'; +import React, { useCallback, useState } from 'react'; +import { IndexPatternField } from '../../../../../data/public'; +import { FieldRecord } from './table'; +import { trimAngularSpan } from './table_helper'; +import { DocViewTableRowBtnCollapse } from './table_row_btn_collapse'; +import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore'; + +const COLLAPSE_LINE_LENGTH = 350; + +type TableFieldValueProps = FieldRecord['value'] & { + fieldName: string; + fieldMapping: IndexPatternField | undefined; +}; + +export const TableFieldValue = ({ + formattedField, + fieldName, + fieldMapping, +}: TableFieldValueProps) => { + const [fieldOpen, setFieldOpen] = useState(false); + + const value = trimAngularSpan(String(formattedField)); + const isCollapsible = value.length > COLLAPSE_LINE_LENGTH; + const isCollapsed = isCollapsible && !fieldOpen; + const displayUnderscoreWarning = !fieldMapping && fieldName.indexOf('_') === 0; + + const valueClassName = classNames({ + // eslint-disable-next-line @typescript-eslint/naming-convention + kbnDocViewer__value: true, + 'truncate-by-height': isCollapsible && isCollapsed, + }); + + const onToggleCollapse = useCallback( + () => setFieldOpen((fieldOpenPrev: boolean) => !fieldOpenPrev), + [] + ); + + return ( + <div> + {isCollapsible && ( + <DocViewTableRowBtnCollapse onClick={onToggleCollapse} isCollapsed={isCollapsed} /> + )} + {displayUnderscoreWarning && <DocViewTableRowIconUnderscore />} + {fieldName ? null : <div className={valueClassName}>{fieldName}: </div>} + <div + className={valueClassName} + data-test-subj={`tableDocViewRow-${fieldName}-value`} + /* + * Justification for dangerouslySetInnerHTML: + * We just use values encoded by our field formatters + */ + // eslint-disable-next-line react/no-danger + dangerouslySetInnerHTML={{ __html: value as string }} + /> + </div> + ); +}; diff --git a/src/plugins/discover/public/application/components/table/table_columns.tsx b/src/plugins/discover/public/application/components/table/table_columns.tsx new file mode 100644 index 0000000000000..462362d543e7a --- /dev/null +++ b/src/plugins/discover/public/application/components/table/table_columns.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiBasicTableColumn, EuiText } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FieldName } from '../field_name/field_name'; +import { FieldRecord } from './table'; +import { TableActions } from './table_cell_actions'; +import { TableFieldValue } from './table_cell_value'; + +export const ACTIONS_COLUMN: EuiBasicTableColumn<FieldRecord> = { + field: 'action', + className: 'kbnDocViewer__tableActionsCell', + width: '108px', + name: ( + <EuiText size="xs"> + <strong> + <FormattedMessage + id="discover.fieldChooser.discoverField.actions" + defaultMessage="Actions" + /> + </strong> + </EuiText> + ), + render: ( + { flattenedField, isActive, onFilter, onToggleColumn }: FieldRecord['action'], + { field: { fieldName, fieldMapping } }: FieldRecord + ) => { + return ( + <TableActions + isActive={isActive} + fieldName={fieldName} + fieldMapping={fieldMapping} + flattenedField={flattenedField} + onFilter={onFilter!} + onToggleColumn={onToggleColumn} + /> + ); + }, +}; + +export const MAIN_COLUMNS: Array<EuiBasicTableColumn<FieldRecord>> = [ + { + field: 'field', + className: 'kbnDocViewer__tableFieldNameCell', + name: ( + <EuiText size="xs"> + <strong> + <FormattedMessage id="discover.fieldChooser.discoverField.name" defaultMessage="Field" /> + </strong> + </EuiText> + ), + render: ({ fieldName, fieldType, fieldMapping, scripted }: FieldRecord['field']) => { + return ( + <FieldName + fieldName={fieldName} + fieldType={fieldType} + fieldMapping={fieldMapping} + scripted={scripted} + /> + ); + }, + }, + { + field: 'value', + name: ( + <EuiText size="xs"> + <strong> + <FormattedMessage id="discover.fieldChooser.discoverField.value" defaultMessage="Value" /> + </strong> + </EuiText> + ), + render: ( + { formattedField }: FieldRecord['value'], + { field: { fieldName, fieldMapping } }: FieldRecord + ) => { + return ( + <TableFieldValue + formattedField={formattedField} + fieldName={fieldName} + fieldMapping={fieldMapping} + /> + ); + }, + }, +]; diff --git a/src/plugins/discover/public/application/components/table/table_row.tsx b/src/plugins/discover/public/application/components/table/table_row.tsx deleted file mode 100644 index e8977fda8576a..0000000000000 --- a/src/plugins/discover/public/application/components/table/table_row.tsx +++ /dev/null @@ -1,112 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import classNames from 'classnames'; -import React, { ReactNode } from 'react'; -import { FieldMapping, DocViewFilterFn } from '../../doc_views/doc_views_types'; -import { DocViewTableRowBtnFilterAdd } from './table_row_btn_filter_add'; -import { DocViewTableRowBtnFilterRemove } from './table_row_btn_filter_remove'; -import { DocViewTableRowBtnToggleColumn } from './table_row_btn_toggle_column'; -import { DocViewTableRowBtnCollapse } from './table_row_btn_collapse'; -import { DocViewTableRowBtnFilterExists } from './table_row_btn_filter_exists'; -import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore'; -import { FieldName } from '../field_name/field_name'; - -export interface Props { - field?: string; - fieldMapping?: FieldMapping; - fieldType: string; - displayUnderscoreWarning: boolean; - isCollapsible: boolean; - isColumnActive: boolean; - isCollapsed: boolean; - onToggleCollapse: () => void; - onFilter?: DocViewFilterFn; - onToggleColumn?: () => void; - value: string | ReactNode; - valueRaw: unknown; -} - -export function DocViewTableRow({ - field, - fieldMapping, - fieldType, - displayUnderscoreWarning, - isCollapsible, - isCollapsed, - isColumnActive, - onFilter, - onToggleCollapse, - onToggleColumn, - value, - valueRaw, -}: Props) { - const valueClassName = classNames({ - // eslint-disable-next-line @typescript-eslint/naming-convention - kbnDocViewer__value: true, - 'truncate-by-height': isCollapsible && isCollapsed, - }); - const key = field ? field : fieldMapping?.displayName; - return ( - <tr key={key} data-test-subj={`tableDocViewRow-${key}`}> - {typeof onFilter === 'function' && ( - <td className="kbnDocViewer__buttons"> - <DocViewTableRowBtnFilterAdd - disabled={!fieldMapping || !fieldMapping.filterable} - onClick={() => onFilter(fieldMapping, valueRaw, '+')} - /> - <DocViewTableRowBtnFilterRemove - disabled={!fieldMapping || !fieldMapping.filterable} - onClick={() => onFilter(fieldMapping, valueRaw, '-')} - /> - {typeof onToggleColumn === 'function' && ( - <DocViewTableRowBtnToggleColumn - active={isColumnActive} - onClick={onToggleColumn} - fieldname={String(key)} - /> - )} - <DocViewTableRowBtnFilterExists - disabled={!fieldMapping || !fieldMapping.filterable} - onClick={() => onFilter('_exists_', field, '+')} - scripted={fieldMapping && fieldMapping.scripted} - /> - </td> - )} - <td className="kbnDocViewer__field"> - {field ? ( - <FieldName - fieldName={field} - fieldType={fieldType} - fieldMapping={fieldMapping} - scripted={Boolean(fieldMapping?.scripted)} - /> - ) : ( - <span> </span> - )} - </td> - <td> - {isCollapsible && ( - <DocViewTableRowBtnCollapse onClick={onToggleCollapse} isCollapsed={isCollapsed} /> - )} - {displayUnderscoreWarning && <DocViewTableRowIconUnderscore />} - {field ? null : <div className={valueClassName}>{key}: </div>} - <div - className={valueClassName} - data-test-subj={`tableDocViewRow-${key}-value`} - /* - * Justification for dangerouslySetInnerHTML: - * We just use values encoded by our field formatters - */ - // eslint-disable-next-line react/no-danger - dangerouslySetInnerHTML={{ __html: value as string }} - /> - </td> - </tr> - ); -} diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index ec89f7516e92d..3e31fe1d46d45 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -36,8 +36,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { UrlGeneratorState } from '../../share/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewsRegistry } from './application/doc_views/doc_views_registry'; -import { DocViewTable } from './application/components/table/table'; - +import { DocViewerTable } from './application/components/table/table'; import { setDocViewsRegistry, setUrlTracker, @@ -252,7 +251,7 @@ export class DiscoverPlugin defaultMessage: 'Table', }), order: 10, - component: DocViewTable, + component: DocViewerTable, }); this.docViewsRegistry.addDocView({ title: i18n.translate('discover.docViews.json.jsonTitle', { From a416e81b49c81a893094f704f2102b267c147f6f Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 29 Jun 2021 07:38:50 -0700 Subject: [PATCH 67/74] [DOCS] Fixes formatting in settings doc (#103519) --- docs/setup/settings.asciidoc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 696b2f042057d..a3f815e5ea504 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -36,7 +36,7 @@ Set to `false` to disable Console. *Default: `true`* <<ops-cGroupOverrides-cpuAcctPath, `ops.cGroupOverrides.cpuAcctPath`>>. | `csp.rules:` - | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] A https://w3c.github.io/webappsec-csp/[Content Security Policy] template that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules @@ -73,8 +73,8 @@ that ship with {kib}. [NOTE] ============ -The `frame-ancestors` directive can also be configured by using -<<server-securityResponseHeaders-disableEmbedding, `server.securityResponseHeaders.disableEmbedding`>>. In that case, that takes precedence and any values in `csp.frame_ancestors` +The `frame-ancestors` directive can also be configured by using +<<server-securityResponseHeaders-disableEmbedding, `server.securityResponseHeaders.disableEmbedding`>>. In that case, that takes precedence and any values in `csp.frame_ancestors` are ignored. ============ @@ -777,10 +777,14 @@ out through *Advanced Settings*. *Default: `true`* | Set this value to true to allow Vega to use any URL to access external data sources and images. When false, Vega can only get data from {es}. *Default: `false`* -| `xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled` +a| +`xpack.discoverEnhanced.actions.` +`exploreDataInContextMenu.enabled` | Enables the *Explore underlying data* option that allows you to open *Discover* from a dashboard panel and view the panel data. *Default: `false`* - | `xpack.discoverEnhanced.actions.exploreDataInChart.enabled` +a| +`xpack.discoverEnhanced.actions.` +`exploreDataInChart.enabled` | Enables you to view the underlying documents in a data series from a dashboard panel. *Default: `false`* | `xpack.license_management.enabled` From 51767e7cb9746d2a7a64e9f9aeabd8242d41b40b Mon Sep 17 00:00:00 2001 From: Vadim Yakhin <yakhin.v@gmail.com> Date: Tue, 29 Jun 2021 11:39:04 -0300 Subject: [PATCH 68/74] Use the user-provided source name in SourceInfoCard (#103542) --- .../content_sources/components/source_layout.test.tsx | 6 ++++++ .../content_sources/components/source_layout.tsx | 11 ++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx index 7c7d77ec418e7..944a54169f0b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx @@ -44,6 +44,12 @@ describe('SourceLayout', () => { expect(wrapper.find('.testChild')).toHaveLength(1); }); + it('passes a source name to SourceInfoCard', () => { + const wrapper = shallow(<SourceLayout />); + + expect(wrapper.find(SourceInfoCard).prop('sourceName')).toEqual('Jira'); + }); + it('renders the default Workplace Search layout when on an organization view', () => { setMockValues({ ...mockValues, isOrganization: true }); const wrapper = shallow(<SourceLayout />); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx index 446e93e0c61f3..663088f797c18 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx @@ -35,19 +35,12 @@ export const SourceLayout: React.FC<PageTemplateProps> = ({ const { contentSource, dataLoading } = useValues(SourceLogic); const { isOrganization } = useValues(AppLogic); - const { - name, - createdAt, - serviceType, - serviceName, - isFederatedSource, - supportedByLicense, - } = contentSource; + const { name, createdAt, serviceType, isFederatedSource, supportedByLicense } = contentSource; const pageHeader = ( <> <SourceInfoCard - sourceName={serviceName} + sourceName={name} sourceType={serviceType} dateCreated={moment(createdAt).format('MMMM D, YYYY')} isFederatedSource={isFederatedSource} From 24661fe20887100a10f25e764c31d171d61049df Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@budzenski.me> Date: Tue, 29 Jun 2021 09:51:11 -0500 Subject: [PATCH 69/74] Revert "[build] Remove OSS builds (#100577)" This reverts commit b2d76a6cd3f96ab152a8aaa81802a048df42051f. --- src/dev/build/README.md | 4 +- src/dev/build/args.test.ts | 7 ++ src/dev/build/args.ts | 3 + src/dev/build/build_distributables.ts | 2 + src/dev/build/cli.ts | 2 + src/dev/build/lib/build.test.ts | 39 +++++--- src/dev/build/lib/build.ts | 12 ++- src/dev/build/lib/runner.test.ts | 92 ++++++++++++++++++- src/dev/build/lib/runner.ts | 8 +- .../tasks/build_kibana_platform_plugins.ts | 2 +- src/dev/build/tasks/build_packages_task.ts | 2 + src/dev/build/tasks/create_archives_task.ts | 4 +- src/dev/build/tasks/install_chromium.js | 24 +++-- src/dev/build/tasks/license_file_task.ts | 16 +++- .../os_packages/create_os_package_tasks.ts | 39 ++++---- .../tasks/os_packages/docker_generator/run.ts | 12 ++- src/dev/build/tasks/os_packages/run_fpm.ts | 12 ++- test/scripts/jenkins_build_kibana.sh | 2 +- test/scripts/jenkins_build_load_testing.sh | 2 +- test/scripts/jenkins_xpack_baseline.sh | 2 +- 20 files changed, 225 insertions(+), 61 deletions(-) diff --git a/src/dev/build/README.md b/src/dev/build/README.md index c499ef4a61083..f6e11af67da33 100644 --- a/src/dev/build/README.md +++ b/src/dev/build/README.md @@ -12,8 +12,8 @@ node scripts/build --help # build a release version node scripts/build --release -# reuse already downloaded node executables, turn on debug logging -node scripts/build --skip-node-download --debug +# reuse already downloaded node executables, turn on debug logging, and only build the default distributable +node scripts/build --skip-node-download --debug --no-oss ``` # Fixing out of memory issues diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index af996321caa5a..e749af73241cf 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -27,6 +27,7 @@ it('build default and oss dist for current platform, without packages, by defaul Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -53,6 +54,7 @@ it('builds packages if --all-platforms is passed', () => { Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": true, @@ -79,6 +81,7 @@ it('limits packages if --rpm passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, @@ -105,6 +108,7 @@ it('limits packages if --deb passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, @@ -132,6 +136,7 @@ it('limits packages if --docker passed with --all-platforms', () => { Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -166,6 +171,7 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, @@ -193,6 +199,7 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () => Object { "buildOptions": Object { "buildDefaultDist": true, + "buildOssDist": true, "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 54da6ac86d3f7..bbfbd3e6f8813 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -15,6 +15,8 @@ export function readCliArgs(argv: string[]) { const unknownFlags: string[] = []; const flags = getopts(argv, { boolean: [ + 'oss', + 'no-oss', 'skip-archives', 'skip-initialize', 'skip-generic-folders', @@ -92,6 +94,7 @@ export function readCliArgs(argv: string[]) { const buildOptions: BuildOptions = { isRelease: Boolean(flags.release), versionQualifier: flags['version-qualifier'], + buildOssDist: flags.oss !== false, buildDefaultDist: !flags.oss, initialize: !Boolean(flags['skip-initialize']), downloadFreshNode: !Boolean(flags['skip-node-download']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index ea543b19030ff..f0403fac1e26b 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -13,6 +13,7 @@ import * as Tasks from './tasks'; export interface BuildOptions { isRelease: boolean; + buildOssDist: boolean; buildDefaultDist: boolean; downloadFreshNode: boolean; initialize: boolean; @@ -37,6 +38,7 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions config, log, buildDefaultDist: options.buildDefaultDist, + buildOssDist: options.buildOssDist, }); /** diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index c727c26d7dcd3..3712e2230296a 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -33,6 +33,8 @@ if (showHelp) { build the Kibana distributable options: + --oss {dim Only produce the OSS distributable of Kibana} + --no-oss {dim Only produce the default distributable of Kibana} --skip-archives {dim Don't produce tar/zip archives} --skip-os-packages {dim Don't produce rpm/deb/docker packages} --all-platforms {dim Produce archives for all platforms, not just this one} diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts index f92a91e2b7221..fe328329d7df0 100644 --- a/src/dev/build/lib/build.test.ts +++ b/src/dev/build/lib/build.test.ts @@ -43,24 +43,40 @@ beforeEach(() => { jest.clearAllMocks(); }); -const defaultBuild = new Build(config); +const ossBuild = new Build(config, true); +const defaultBuild = new Build(config, false); + +describe('#isOss()', () => { + it('returns true for oss', () => { + expect(ossBuild.isOss()).toBe(true); + }); + + it('returns false for default build', () => { + expect(defaultBuild.isOss()).toBe(false); + }); +}); describe('#getName()', () => { it('returns kibana for default build', () => { expect(defaultBuild.getName()).toBe('kibana'); }); + + it('returns kibana-oss for oss', () => { + expect(ossBuild.getName()).toBe('kibana-oss'); + }); }); describe('#getLogTag()', () => { it('returns string with build name in it', () => { expect(defaultBuild.getLogTag()).toContain(defaultBuild.getName()); + expect(ossBuild.getLogTag()).toContain(ossBuild.getName()); }); }); describe('#resolvePath()', () => { it('uses passed config to resolve a path relative to the repo', () => { - expect(defaultBuild.resolvePath('bar')).toMatchInlineSnapshot( - `<absolute path>/build/kibana/bar` + expect(ossBuild.resolvePath('bar')).toMatchInlineSnapshot( + `<absolute path>/build/kibana-oss/bar` ); }); @@ -73,27 +89,28 @@ describe('#resolvePath()', () => { describe('#resolvePathForPlatform()', () => { it('uses config.resolveFromRepo(), config.getBuildVersion(), and platform.getBuildName() to create path', () => { - expect(defaultBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( - `<absolute path>/build/default/kibana-8.0.0-linux-x86_64/foo/bar` + expect(ossBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot( + `<absolute path>/build/oss/kibana-8.0.0-linux-x86_64/foo/bar` ); }); }); describe('#getPlatformArchivePath()', () => { it('creates correct path for different platforms', () => { - expect(defaultBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-8.0.0-linux-x86_64.tar.gz` + expect(ossBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-oss-8.0.0-linux-x86_64.tar.gz` ); - expect(defaultBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-8.0.0-linux-aarch64.tar.gz` + expect(ossBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-oss-8.0.0-linux-aarch64.tar.gz` ); - expect(defaultBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( - `<absolute path>/target/kibana-8.0.0-windows-x86_64.zip` + expect(ossBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot( + `<absolute path>/target/kibana-oss-8.0.0-windows-x86_64.zip` ); }); describe('#getRootDirectory()', () => { it('creates correct root directory name', () => { + expect(ossBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-oss-8.0.0"`); expect(defaultBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-8.0.0"`); }); }); diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts index c777ad18dc51f..f4ccb30994eef 100644 --- a/src/dev/build/lib/build.ts +++ b/src/dev/build/lib/build.ts @@ -12,10 +12,14 @@ import { Config } from './config'; import { Platform } from './platform'; export class Build { - private name = 'kibana'; - private logTag = chalk`{cyan [ kibana ]}`; + private name = this.oss ? 'kibana-oss' : 'kibana'; + private logTag = this.oss ? chalk`{magenta [kibana-oss]}` : chalk`{cyan [ kibana ]}`; - constructor(private config: Config) {} + constructor(private config: Config, private oss: boolean) {} + + isOss() { + return !!this.oss; + } resolvePath(...args: string[]) { return this.config.resolveFromRepo('build', this.name, ...args); @@ -24,7 +28,7 @@ export class Build { resolvePathForPlatform(platform: Platform, ...args: string[]) { return this.config.resolveFromRepo( 'build', - 'default', + this.oss ? 'oss' : 'default', `kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`, ...args ); diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts index 581cbadeb5061..0e3c00d220ad9 100644 --- a/src/dev/build/lib/runner.test.ts +++ b/src/dev/build/lib/runner.test.ts @@ -45,7 +45,7 @@ beforeEach(() => { jest.clearAllMocks(); }); -const setup = async (opts: { buildDefaultDist: boolean }) => { +const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) => { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, @@ -61,10 +61,48 @@ const setup = async (opts: { buildDefaultDist: boolean }) => { return { config, run }; }; -describe('default dist', () => { +describe('buildOssDist = true, buildDefaultDist = true', () => { it('runs global task once, passing config and log', async () => { const { config, run } = await setup({ buildDefaultDist: true, + buildOssDist: true, + }); + + const mock = jest.fn(); + + await run({ + global: true, + description: 'foo', + run: mock, + }); + + expect(mock).toHaveBeenCalledTimes(1); + expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build), expect.any(Build)]); + }); + + it('calls local tasks twice, passing each build', async () => { + const { config, run } = await setup({ + buildDefaultDist: true, + buildOssDist: true, + }); + + const mock = jest.fn(); + + await run({ + description: 'foo', + run: mock, + }); + + expect(mock).toHaveBeenCalledTimes(2); + expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); + }); +}); + +describe('just default dist', () => { + it('runs global task once, passing config and log', async () => { + const { config, run } = await setup({ + buildDefaultDist: true, + buildOssDist: false, }); const mock = jest.fn(); @@ -82,6 +120,49 @@ describe('default dist', () => { it('calls local tasks once, passing the default build', async () => { const { config, run } = await setup({ buildDefaultDist: true, + buildOssDist: false, + }); + + const mock = jest.fn(); + + await run({ + description: 'foo', + run: mock, + }); + + expect(mock).toHaveBeenCalledTimes(1); + expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); + const [args] = mock.mock.calls; + const [, , build] = args; + if (build.isOss()) { + throw new Error('expected build to be the default dist, not the oss dist'); + } + }); +}); + +describe('just oss dist', () => { + it('runs global task once, passing config and log', async () => { + const { config, run } = await setup({ + buildDefaultDist: false, + buildOssDist: true, + }); + + const mock = jest.fn(); + + await run({ + global: true, + description: 'foo', + run: mock, + }); + + expect(mock).toHaveBeenCalledTimes(1); + expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build)]); + }); + + it('calls local tasks once, passing the oss build', async () => { + const { config, run } = await setup({ + buildDefaultDist: false, + buildOssDist: true, }); const mock = jest.fn(); @@ -93,6 +174,11 @@ describe('default dist', () => { expect(mock).toHaveBeenCalledTimes(1); expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build)); + const [args] = mock.mock.calls; + const [, , build] = args; + if (!build.isOss()) { + throw new Error('expected build to be the oss dist, not the default dist'); + } }); }); @@ -100,6 +186,7 @@ describe('task rejection', () => { it('rejects, logs error, and marks error logged', async () => { const { run } = await setup({ buildDefaultDist: true, + buildOssDist: false, }); const error = new Error('FOO'); @@ -128,6 +215,7 @@ describe('task rejection', () => { it('just rethrows errors that have already been logged', async () => { const { run } = await setup({ buildDefaultDist: true, + buildOssDist: false, }); const error = markErrorLogged(new Error('FOO')); diff --git a/src/dev/build/lib/runner.ts b/src/dev/build/lib/runner.ts index eccde9160f8a3..015de6fe7e9ef 100644 --- a/src/dev/build/lib/runner.ts +++ b/src/dev/build/lib/runner.ts @@ -16,6 +16,7 @@ import { Config } from './config'; interface Options { config: Config; log: ToolingLog; + buildOssDist: boolean; buildDefaultDist: boolean; } @@ -31,7 +32,7 @@ export interface Task { run(config: Config, log: ToolingLog, build: Build): Promise<void>; } -export function createRunner({ config, log, buildDefaultDist }: Options) { +export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Options) { async function execTask(desc: string, task: Task | GlobalTask, lastArg: any) { log.info(desc); log.indent(4); @@ -63,7 +64,10 @@ export function createRunner({ config, log, buildDefaultDist }: Options) { const builds: Build[] = []; if (buildDefaultDist) { - builds.push(new Build(config)); + builds.push(new Build(config, false)); + } + if (buildOssDist) { + builds.push(new Build(config, true)); } /** diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts index 7d8690df6f3ce..edff77d458f0f 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.ts +++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts @@ -27,7 +27,7 @@ export const BuildKibanaPlatformPlugins: Task = { repoRoot: REPO_ROOT, outputRoot: build.resolvePath(), cache: false, - oss: false, + oss: build.isOss(), examples: false, watch: false, dist: true, diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts index 808903661a595..e6305b3761a4f 100644 --- a/src/dev/build/tasks/build_packages_task.ts +++ b/src/dev/build/tasks/build_packages_task.ts @@ -63,6 +63,7 @@ export const BuildBazelPackages: Task = { await buildBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), + onlyOSS: build.isOss(), }); }, }; @@ -74,6 +75,7 @@ export const BuildPackages: Task = { await buildNonBazelProductionProjects({ kibanaRoot: config.resolveFromRepo(), buildRoot: build.resolvePath(), + onlyOSS: build.isOss(), }); }, }; diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts index 37c4becae76a8..e2d3ff7149c4c 100644 --- a/src/dev/build/tasks/create_archives_task.ts +++ b/src/dev/build/tasks/create_archives_task.ts @@ -77,14 +77,14 @@ export const CreateArchives: Task = { const metrics: CiStatsMetric[] = []; for (const { format, path, fileCount } of archives) { metrics.push({ - group: `distributable size`, + group: `${build.isOss() ? 'oss ' : ''}distributable size`, id: format, value: (await asyncStat(path)).size, }); metrics.push({ group: 'distributable file count', - id: 'default', + id: build.isOss() ? 'oss' : 'default', value: fileCount, }); } diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index 37abcbad4466e..b21a8484fa710 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -15,17 +15,21 @@ export const InstallChromium = { description: 'Installing Chromium', async run(config, log, build) { - for (const platform of config.getNodePlatforms()) { - log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); + if (build.isOss()) { + return; + } else { + for (const platform of config.getNodePlatforms()) { + log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); - const { binaryPath$ } = installBrowser( - // TODO: https://github.com/elastic/kibana/issues/72496 - log, - build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), - platform.getName(), - platform.getArchitecture() - ); - await binaryPath$.pipe(first()).toPromise(); + const { binaryPath$ } = installBrowser( + // TODO: https://github.com/elastic/kibana/issues/72496 + log, + build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), + platform.getName(), + platform.getArchitecture() + ); + await binaryPath$.pipe(first()).toPromise(); + } } }, }; diff --git a/src/dev/build/tasks/license_file_task.ts b/src/dev/build/tasks/license_file_task.ts index ff33707476576..7e5ed8da0a27a 100644 --- a/src/dev/build/tasks/license_file_task.ts +++ b/src/dev/build/tasks/license_file_task.ts @@ -8,13 +8,23 @@ import { write, read, Task } from '../lib'; +const LICENSE_SEPARATOR = `\n------------------------------------------------------------------------\n\n`; + export const UpdateLicenseFile: Task = { description: 'Updating LICENSE.txt file', async run(config, log, build) { const elasticLicense = await read(config.resolveFromRepo('licenses/ELASTIC-LICENSE-2.0.txt')); - - log.info('Copying Elastic license to LICENSE.txt'); - await write(build.resolvePath('LICENSE.txt'), elasticLicense); + if (build.isOss()) { + const ssplLicense = await read(config.resolveFromRepo('licenses/SSPL-LICENSE.txt')); + log.info('Copying dual-license to LICENSE.txt'); + await write( + build.resolvePath('LICENSE.txt'), + ssplLicense + LICENSE_SEPARATOR + elasticLicense + ); + } else { + log.info('Copying Elastic license to LICENSE.txt'); + await write(build.resolvePath('LICENSE.txt'), elasticLicense); + } }, }; diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 99d0e1998e78a..2ae882000cae0 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -73,12 +73,15 @@ export const CreateDockerUBI: Task = { description: 'Creating Docker UBI image', async run(config, log, build) { - await runDockerGenerator(config, log, build, { - architecture: 'x64', - context: false, - ubi: true, - image: true, - }); + if (!build.isOss()) { + await runDockerGenerator(config, log, build, { + architecture: 'x64', + context: false, + ubi: true, + image: true, + dockerBuildDate, + }); + } }, }; @@ -92,15 +95,19 @@ export const CreateDockerContexts: Task = { dockerBuildDate, }); - await runDockerGenerator(config, log, build, { - ubi: true, - context: true, - image: false, - }); - await runDockerGenerator(config, log, build, { - ironbank: true, - context: true, - image: false, - }); + if (!build.isOss()) { + await runDockerGenerator(config, log, build, { + ubi: true, + context: true, + image: false, + dockerBuildDate, + }); + await runDockerGenerator(config, log, build, { + ironbank: true, + context: true, + image: false, + dockerBuildDate, + }); + } }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 97fd740409741..c72112b7b6b03 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -43,18 +43,24 @@ export async function runDockerGenerator( let imageFlavor = ''; if (flags.ubi) imageFlavor += `-${ubiVersionTag}`; if (flags.ironbank) imageFlavor += '-ironbank'; + if (build.isOss()) imageFlavor += '-oss'; // General docker var config - const license = 'Elastic License'; + const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; const imageTag = 'docker.elastic.co/kibana/kibana'; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; - const artifactPrefix = `kibana-${version}-linux`; + const artifactFlavor = build.isOss() ? '-oss' : ''; + const artifactPrefix = `kibana${artifactFlavor}-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString(); // That would produce oss, default and default-ubi7 - const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`); + const dockerBuildDir = config.resolveFromRepo( + 'build', + 'kibana-docker', + build.isOss() ? `oss` : `default${imageFlavor}` + ); const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( `kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index b732e4c80ea37..933b3e411b286 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -28,7 +28,11 @@ export async function runFpm( const fromBuild = (...paths: string[]) => build.resolvePathForPlatform(linux, ...paths); const pickLicense = () => { - return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; + if (build.isOss()) { + return type === 'rpm' ? 'ASL 2.0' : 'ASL-2.0'; + } else { + return type === 'rpm' ? 'Elastic License' : 'Elastic-License'; + } }; const envFolder = type === 'rpm' ? 'sysconfig' : 'default'; @@ -53,7 +57,7 @@ export async function runFpm( // general info about the package '--name', - 'kibana', + build.isOss() ? 'kibana-oss' : 'kibana', '--description', 'Explore and visualize your Elasticsearch data', '--version', @@ -67,6 +71,10 @@ export async function runFpm( '--license', pickLicense(), + // prevent installing kibana if installing kibana-oss and vice versa + '--conflicts', + build.isOss() ? 'kibana' : 'kibana-oss', + // define install/uninstall scripts '--after-install', resolve(__dirname, 'package_scripts/post_install.sh'), diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index a9edd3ed2a701..198723908cf48 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -33,7 +33,7 @@ node x-pack/scripts/functional_tests --assert-none-excluded \ # Do not build kibana for code coverage run if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug + node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index d7c7bda83c9ef..5571eee4f28ed 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -60,7 +60,7 @@ export KBN_NP_PLUGINS_BUILT=true echo " -> Building and extracting default Kibana distributable for use in functional tests" cd "$KIBANA_DIR" -node scripts/build --debug +node scripts/build --debug --no-oss linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" mkdir -p "$installDir" diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index c68c0f40902f2..8d5624949505a 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -5,7 +5,7 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" echo " -> building and extracting default Kibana distributable" cd "$KIBANA_DIR" -node scripts/build --debug +node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ From 6ee79558ab9b8b3c29a4603a88c981ee34587eeb Mon Sep 17 00:00:00 2001 From: Pablo Machado <pablo.nevesmachado@elastic.co> Date: Tue, 29 Jun 2021 17:00:30 +0200 Subject: [PATCH 70/74] [Security Solution][Detections] Fix Investigation guide format issues (#101609) * Fix 'Detection' / 'Investigation Guide' UI broken when it contains long words * Fix investigation guide is not formatted under Alert details flyout * Add LineClamp to investigation guide field * It enhances LineClamp to support a react node instead of only text Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../event_details/alert_summary_view.tsx | 5 ++- .../components/line_clamp/index.test.tsx | 39 +++++++++-------- .../common/components/line_clamp/index.tsx | 42 +++++++++++-------- .../rules/step_about_rule_details/index.tsx | 1 + .../components/flyout/header/index.tsx | 5 ++- .../event_details/expandable_event.tsx | 2 +- 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index d89f44542318e..be45e16e456d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -36,6 +36,7 @@ import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../networ import { SummaryView } from './summary_view'; import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; +import { MarkdownRenderer } from '../markdown_editor'; import { LineClamp } from '../line_clamp'; import { endpointAlertCheck } from '../../utils/endpoint_alert_check'; @@ -221,7 +222,9 @@ const AlertSummaryViewComponent: React.FC<{ <StyledEuiDescriptionList data-test-subj={`summary-view-guide`} compressed> <EuiDescriptionListTitle>{i18n.INVESTIGATION_GUIDE}</EuiDescriptionListTitle> <EuiDescriptionListDescription> - <LineClamp content={maybeRule?.note} /> + <LineClamp> + <MarkdownRenderer>{maybeRule.note}</MarkdownRenderer> + </LineClamp> </EuiDescriptionListDescription> </StyledEuiDescriptionList> )} diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.test.tsx index 73f46a1771030..933517c49690c 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.test.tsx @@ -16,25 +16,30 @@ describe('LineClamp', () => { describe('no overflow', () => { test('it does NOT render the expanded line clamp when isOverflow is falsy', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').exists()).toBe(false); }); test('it does NOT render the styled line clamp expanded when isOverflow is falsy', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="styled-line-clamp"]').exists()).toBe(false); }); - test('it renders the default line clamp when isOverflow is falsy', () => { - const wrapper = mount(<LineClamp content={message} />); + test('it renders the children when isOverflow is falsy', () => { + const TestComponent = () => <>{message}</>; + const wrapper = mount( + <LineClamp> + <TestComponent /> + </LineClamp> + ); - expect(wrapper.find('[data-test-subj="default-line-clamp"]').first().text()).toBe(message); + expect(wrapper.childAt(0).type()).toBe(TestComponent); }); test('it does NOT render the `Read More` button when isOverflow is falsy', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="summary-view-readmore"]').exists()).toBe(false); }); @@ -59,25 +64,25 @@ describe('LineClamp', () => { }); test('it does NOT render the expanded line clamp by default when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="expanded-line-clamp"]').exists()).toBe(false); }); test('it renders the styled line clamp when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="styled-line-clamp"]').first().text()).toBe(message); }); test('it does NOT render the default line clamp when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="default-line-clamp"]').exists()).toBe(false); }); test('it renders the `Read More` button with the expected (default) text when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); expect(wrapper.find('[data-test-subj="summary-view-readmore"]').first().text()).toBe( 'Read More' @@ -86,7 +91,7 @@ describe('LineClamp', () => { describe('clicking the Read More button', () => { test('it displays the `Read Less` button text after the user clicks the `Read More` button when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -97,7 +102,7 @@ describe('LineClamp', () => { }); test('it renders the expanded content after the user clicks the `Read More` button when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -107,7 +112,7 @@ describe('LineClamp', () => { }); test('it renders the expanded content with a max-height of one third the view height when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -119,7 +124,7 @@ describe('LineClamp', () => { }); test('it automatically vertically scrolls the content when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -131,7 +136,7 @@ describe('LineClamp', () => { }); test('it does NOT render the styled line clamp after the user clicks the `Read More` button when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -140,7 +145,7 @@ describe('LineClamp', () => { }); test('it does NOT render the default line clamp after the user clicks the `Read More` button when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); @@ -149,7 +154,7 @@ describe('LineClamp', () => { }); test('it once again displays the `Read More` button text after the user clicks the `Read Less` when isOverflow is true', () => { - const wrapper = mount(<LineClamp content={message} />); + const wrapper = mount(<LineClamp>{message}</LineClamp>); wrapper.find('[data-test-subj="summary-view-readmore"]').first().simulate('click'); wrapper.update(); // 1st toggle diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index d8895490d1e0f..372e7fd466b07 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { EuiButtonEmpty, EuiText } from '@elastic/eui'; -import React, { useRef, useState, useEffect, useCallback } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import React, { useRef, useState, useEffect, useCallback, ReactNode } from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -36,9 +36,9 @@ const StyledLineClamp = styled.div<{ lineClampHeight: number }>` `; const LineClampComponent: React.FC<{ - content?: string | null; + children: ReactNode; lineClampHeight?: number; -}> = ({ content, lineClampHeight = LINE_CLAMP_HEIGHT }) => { +}> = ({ children, lineClampHeight = LINE_CLAMP_HEIGHT }) => { const [isOverflow, setIsOverflow] = useState<boolean | null>(null); const [isExpanded, setIsExpanded] = useState<boolean | null>(null); const descriptionRef = useRef<HTMLDivElement>(null); @@ -47,7 +47,7 @@ const LineClampComponent: React.FC<{ }, []); useEffect(() => { - if (content != null && descriptionRef?.current?.clientHeight != null) { + if (descriptionRef?.current?.clientHeight != null) { if ( (descriptionRef?.current?.scrollHeight ?? 0) > (descriptionRef?.current?.clientHeight ?? 0) ) { @@ -55,38 +55,44 @@ const LineClampComponent: React.FC<{ } if ( - ((content == null || descriptionRef?.current?.scrollHeight) ?? 0) <= - (descriptionRef?.current?.clientHeight ?? 0) + (descriptionRef?.current?.scrollHeight ?? 0) <= (descriptionRef?.current?.clientHeight ?? 0) ) { setIsOverflow(false); } } - }, [content]); + }, []); - if (!content) { - return null; + if (isExpanded) { + return ( + <> + <ExpandedContent data-test-subj="expanded-line-clamp"> + <p>{children}</p> + </ExpandedContent> + {isOverflow && ( + <ReadMore onClick={toggleReadMore} size="s" data-test-subj="summary-view-readmore"> + {i18n.READ_LESS} + </ReadMore> + )} + </> + ); } return ( <> - {isExpanded ? ( - <ExpandedContent data-test-subj="expanded-line-clamp"> - <p>{content}</p> - </ExpandedContent> - ) : isOverflow == null || isOverflow === true ? ( + {isOverflow == null || isOverflow === true ? ( <StyledLineClamp data-test-subj="styled-line-clamp" ref={descriptionRef} lineClampHeight={lineClampHeight} > - {content} + {children} </StyledLineClamp> ) : ( - <EuiText data-test-subj="default-line-clamp">{content}</EuiText> + children )} {isOverflow && ( <ReadMore onClick={toggleReadMore} size="s" data-test-subj="summary-view-readmore"> - {isExpanded ? i18n.READ_LESS : i18n.READ_MORE} + {i18n.READ_MORE} </ReadMore> )} </> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx index c1078e1ba77e7..f400887f43927 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx @@ -37,6 +37,7 @@ const FlexGroupFullHeight = styled(EuiFlexGroup)` const VerticalOverflowContainer = styled.div((props: { maxHeight: number }) => ({ 'max-height': `${props.maxHeight}px`, 'overflow-y': 'hidden', + 'word-break': 'break-word', })); const VerticalOverflowContent = styled.div((props: { maxHeight: number }) => ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 216282b72920c..479b32c2d642e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -206,11 +206,12 @@ const TimelineDescriptionComponent: React.FC<FlyoutHeaderProps> = ({ timelineId const description = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).description ); - return ( <EuiText size="s" data-test-subj="timeline-description"> {description.length ? ( - <LineClamp key={description.length} content={description} lineClampHeight={4.5} /> + <LineClamp key={description.length} lineClampHeight={4.5}> + {description} + </LineClamp> ) : ( commonI18n.DESCRIPTION )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index d1d5bffc6bd0a..fff2d4559c8df 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -113,7 +113,7 @@ export const ExpandableEvent = React.memo<Props>( <EuiDescriptionList data-test-subj="event-message" compressed> <EuiDescriptionListTitle>{i18n.MESSAGE}</EuiDescriptionListTitle> <EuiDescriptionListDescription> - <LineClamp content={message} /> + <LineClamp>{message}</LineClamp> </EuiDescriptionListDescription> </EuiDescriptionList> <EuiSpacer size="m" /> From 0ba4153d4b58c7fc72458102ce305463a1ae077f Mon Sep 17 00:00:00 2001 From: Alexey Antonov <alexwizp@gmail.com> Date: Tue, 29 Jun 2021 18:14:48 +0300 Subject: [PATCH 71/74] [TSVB] fix wrong labels, for values that are implicitly cast to false (#103631) * [TSVB] fix wrong labels, for values that are implicitly cast to false * getMeaningfulValueOrEmpty -> getValueOrEmpty * fix CI --- src/plugins/vis_type_timeseries/common/empty_label.ts | 7 +++++++ .../application/components/lib/convert_series_to_vars.js | 9 +++++++-- .../public/application/components/vis_types/table/vis.js | 4 ++-- .../public/application/components/vis_with_splits.js | 4 ++-- .../public/application/lib/get_split_by_terms_color.ts | 4 ++-- .../application/visualizations/views/timeseries/index.js | 6 +++--- .../public/application/visualizations/views/top_n.js | 4 ++-- 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/plugins/vis_type_timeseries/common/empty_label.ts b/src/plugins/vis_type_timeseries/common/empty_label.ts index d55a58f17dbf3..d95e8fe3f7f16 100644 --- a/src/plugins/vis_type_timeseries/common/empty_label.ts +++ b/src/plugins/vis_type_timeseries/common/empty_label.ts @@ -11,3 +11,10 @@ import { i18n } from '@kbn/i18n'; export const emptyLabel = i18n.translate('visTypeTimeseries.emptyTextValue', { defaultMessage: '(empty)', }); + +export const getValueOrEmpty = (value: unknown) => { + if (value === '' || value === null || value === undefined) { + return emptyLabel; + } + return `${value}`; +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js index 3616a8c8b348d..816bce5dac75b 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/convert_series_to_vars.js @@ -9,7 +9,7 @@ import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import { getLastValue } from '../../../../common/last_value_utils'; -import { emptyLabel } from '../../../../common/empty_label'; +import { getValueOrEmpty, emptyLabel } from '../../../../common/empty_label'; import { createTickFormatter } from './tick_formatter'; import { labelDateFormatter } from './label_date_formatter'; import moment from 'moment'; @@ -20,7 +20,12 @@ export const convertSeriesToVars = (series, model, dateFormat = 'lll', getConfig series .filter((row) => _.startsWith(row.id, seriesModel.id)) .forEach((row) => { - const label = row.label ? _.snakeCase(row.label) : emptyLabel; + let label = getValueOrEmpty(row.label); + + if (label !== emptyLabel) { + label = _.snakeCase(label); + } + const varName = [label, _.snakeCase(seriesModel.var_name)].filter((v) => v).join('.'); const formatter = createTickFormatter( diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 8f19644132d3f..4dd8f672c9ea3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -18,7 +18,7 @@ import { replaceVars } from '../../lib/replace_vars'; import { fieldFormats } from '../../../../../../../plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { getFieldFormats, getCoreStart } from '../../../../services'; -import { emptyLabel } from '../../../../../common/empty_label'; +import { getValueOrEmpty } from '../../../../../common/empty_label'; function getColor(rules, colorKey, value) { let color; @@ -98,7 +98,7 @@ class TableVis extends Component { }); return ( <tr key={row.key}> - <td>{rowDisplay || emptyLabel}</td> + <td>{getValueOrEmpty(rowDisplay)}</td> {columns} </tr> ); diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js index 4b933bc81d882..e55c5d708e481 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js @@ -10,7 +10,7 @@ import React, { useCallback } from 'react'; import { getDisplayName } from './lib/get_display_name'; import { labelDateFormatter } from './lib/label_date_formatter'; import { findIndex, first } from 'lodash'; -import { emptyLabel } from '../../../common/empty_label'; +import { getValueOrEmpty } from '../../../common/empty_label'; import { getSplitByTermsColor } from '../lib/get_split_by_terms_color'; export function visWithSplits(WrappedComponent) { @@ -110,7 +110,7 @@ export function visWithSplits(WrappedComponent) { visData={newVisData} onBrush={props.onBrush} onFilterClick={props.onFilterClick} - additionalLabel={additionalLabel || emptyLabel} + additionalLabel={getValueOrEmpty(additionalLabel)} backgroundColor={props.backgroundColor} getConfig={props.getConfig} /> diff --git a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts index 028ce3d028997..e02965c1b0657 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts +++ b/src/plugins/vis_type_timeseries/public/application/lib/get_split_by_terms_color.ts @@ -10,7 +10,7 @@ import { PALETTES } from '../../../common/enums'; import type { PanelData } from '../../../common/types'; import { computeGradientFinalColor } from './compute_gradient_final_color'; import { rainbowColors } from './rainbow_colors'; -import { emptyLabel } from '../../../common/empty_label'; +import { getValueOrEmpty } from '../../../common/empty_label'; interface PaletteParams { colors: string[]; @@ -61,7 +61,7 @@ export const getSplitByTermsColor = ({ const outputColor = palettesRegistry?.get(paletteName || 'default').getCategoricalColor( [ { - name: seriesName || emptyLabel, + name: getValueOrEmpty(seriesName), rankAtDepth: seriesById.findIndex(({ id }) => id === seriesId), totalSeriesAtDepth: seriesById.length, }, diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index a4d834ea8d217..ed62c0909e51b 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -31,7 +31,7 @@ import { BarSeriesDecorator } from './decorators/bar_decorator'; import { getStackAccessors } from './utils/stack_format'; import { getBaseTheme, getChartClasses } from './utils/theme'; import { TOOLTIP_MODES } from '../../../../../common/enums'; -import { emptyLabel } from '../../../../../common/empty_label'; +import { getValueOrEmpty } from '../../../../../common/empty_label'; import { getSplitByTermsColor } from '../../../lib/get_split_by_terms_color'; import { renderEndzoneTooltip } from '../../../../../../charts/public'; import { getAxisLabelString } from '../../../components/lib/get_axis_label_string'; @@ -237,7 +237,7 @@ export const TimeSeries = ({ key={key} seriesId={id} seriesGroupId={groupId} - name={seriesName || emptyLabel} + name={getValueOrEmpty(seriesName)} data={data} hideInLegend={hideInLegend} bars={bars} @@ -262,7 +262,7 @@ export const TimeSeries = ({ key={key} seriesId={id} seriesGroupId={groupId} - name={seriesName || emptyLabel} + name={getValueOrEmpty(seriesName)} data={data} hideInLegend={hideInLegend} lines={lines} diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js index 0c43ab157fbbb..9d6381f21b11f 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { getLastValue, isEmptyValue } from '../../../../common/last_value_utils'; import { labelDateFormatter } from '../../components/lib/label_date_formatter'; -import { emptyLabel } from '../../../../common/empty_label'; +import { getValueOrEmpty } from '../../../../common/empty_label'; import reactcss from 'reactcss'; const RENDER_MODES = { @@ -131,7 +131,7 @@ export class TopN extends Component { return ( <tr key={key} onClick={this.handleClick({ lastValue, ...item })} style={styles.row}> <td title={item.label} className="tvbVisTopN__label" style={styles.label}> - {label || emptyLabel} + {getValueOrEmpty(label)} </td> <td width="100%" className="tvbVisTopN__bar"> <div className="tvbVisTopN__innerBar" style={styles.innerBar}> From 2f4b9f59166f0ba5343520a192eac39511ce2f7d Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 29 Jun 2021 16:24:30 +0100 Subject: [PATCH 72/74] revert cypress (#103658) --- .../cypress/integration/cases/attach_timeline.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts index b8477d5b08280..29105ce1582cf 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases/attach_timeline.spec.ts @@ -21,12 +21,10 @@ import { createCase } from '../../tasks/api_calls/cases'; describe('attach timeline to case', () => { context('without cases created', () => { - beforeEach((done) => { + beforeEach(() => { cleanKibana(); - createTimeline(timeline).then((response) => { cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline'); - done(); }); }); From e387d3d98f4a88ab8503ddaad16b1dc839823564 Mon Sep 17 00:00:00 2001 From: Alison Goryachev <alison.goryachev@elastic.co> Date: Tue, 29 Jun 2021 11:32:41 -0400 Subject: [PATCH 73/74] [Snapshot + Restore] Set snapshots response size limit (#103331) --- .../public/doc_links/doc_links_service.ts | 1 + .../helpers/home.helpers.ts | 5 +- .../__jest__/client_integration/home.test.ts | 74 ++++++++++++- .../snapshot_restore/common/constants.ts | 6 + .../common/lib/snapshot_serialization.test.ts | 3 +- .../common/lib/snapshot_serialization.ts | 2 +- .../snapshot_restore/common/types/snapshot.ts | 1 + .../home/snapshot_list/snapshot_list.tsx | 45 +++++++- .../server/routes/api/repositories.test.ts | 7 +- .../server/routes/api/repositories.ts | 7 +- .../server/routes/api/snapshots.test.ts | 89 +++++++++------ .../server/routes/api/snapshots.ts | 104 ++++++------------ .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 14 files changed, 223 insertions(+), 131 deletions(-) diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 43c21b37ee298..9206a4d1b99f1 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -355,6 +355,7 @@ export class DocLinksService { guide: `${KIBANA_DOCS}snapshot-repositories.html`, changeIndexSettings: `${ELASTICSEARCH_DOCS}snapshots-restore-snapshot.html#change-index-settings-during-restore`, createSnapshot: `${ELASTICSEARCH_DOCS}snapshots-take-snapshot.html`, + getSnapshot: `${ELASTICSEARCH_DOCS}get-snapshot-api.html`, registerSharedFileSystem: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-filesystem-repository`, registerSourceOnly: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-source-only-repository`, registerUrl: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-read-only-repository`, diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 00cec284f3747..238d3e0440493 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -379,4 +379,7 @@ export type TestSubjects = | 'verifyRepositoryButton' | 'version' | 'version.title' - | 'version.value'; + | 'version.value' + | 'maxSnapshotsWarning' + | 'repositoryErrorsWarning' + | 'repositoryErrorsPrompt'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 9f334ce4d49c8..a1f86e25d97fd 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -33,6 +33,16 @@ jest.mock('@kbn/i18n/react', () => { }; }); +jest.mock('../../common/constants', () => { + const original = jest.requireActual('../../common/constants'); + + return { + ...original, + // Mocking this value to a lower number in order to more easily trigger the max snapshots warning in the tests + SNAPSHOT_LIST_MAX_SIZE: 2, + }; +}); + const removeWhiteSpaceOnArrayValues = (array: any[]) => array.map((value) => { if (!value.trim) { @@ -461,7 +471,6 @@ describe('<SnapshotRestoreHome />', () => { httpRequestsMockHelpers.setLoadSnapshotsResponse({ snapshots, repositories: [REPOSITORY_NAME], - errors: {}, }); testBed = await setup(); @@ -494,6 +503,69 @@ describe('<SnapshotRestoreHome />', () => { }); }); + test('should show a warning if the number of snapshots exceeded the limit', () => { + // We have mocked the SNAPSHOT_LIST_MAX_SIZE to 2, so the warning should display + const { find, exists } = testBed; + expect(exists('maxSnapshotsWarning')).toBe(true); + expect(find('maxSnapshotsWarning').text()).toContain( + 'Cannot show the full list of snapshots' + ); + }); + + test('should show a warning if one repository contains errors', async () => { + httpRequestsMockHelpers.setLoadSnapshotsResponse({ + snapshots, + repositories: [REPOSITORY_NAME], + errors: { + repository_with_errors: { + type: 'repository_exception', + reason: + '[repository_with_errors] Could not read repository data because the contents of the repository do not match its expected state.', + }, + }, + }); + + testBed = await setup(); + + await act(async () => { + testBed.actions.selectTab('snapshots'); + }); + + testBed.component.update(); + + const { find, exists } = testBed; + expect(exists('repositoryErrorsWarning')).toBe(true); + expect(find('repositoryErrorsWarning').text()).toContain( + 'Some repositories contain errors' + ); + }); + + test('should show a prompt if a repository contains errors and there are no other repositories', async () => { + httpRequestsMockHelpers.setLoadSnapshotsResponse({ + snapshots, + repositories: [], + errors: { + repository_with_errors: { + type: 'repository_exception', + reason: + '[repository_with_errors] Could not read repository data because the contents of the repository do not match its expected state.', + }, + }, + }); + + testBed = await setup(); + + await act(async () => { + testBed.actions.selectTab('snapshots'); + }); + + testBed.component.update(); + + const { find, exists } = testBed; + expect(exists('repositoryErrorsPrompt')).toBe(true); + expect(find('repositoryErrorsPrompt').text()).toContain('Some repositories contain errors'); + }); + test('each row should have a link to the repository', async () => { const { component, find, exists, table, router } = testBed; diff --git a/x-pack/plugins/snapshot_restore/common/constants.ts b/x-pack/plugins/snapshot_restore/common/constants.ts index b18e118dc5ff6..a7c83ecf702e0 100644 --- a/x-pack/plugins/snapshot_restore/common/constants.ts +++ b/x-pack/plugins/snapshot_restore/common/constants.ts @@ -65,3 +65,9 @@ export const TIME_UNITS: { [key: string]: 'd' | 'h' | 'm' | 's' } = { MINUTE: 'm', SECOND: 's', }; + +/** + * [Temporary workaround] In order to prevent client-side performance issues for users with a large number of snapshots, + * we set a hard-coded limit on the number of snapshots we return from the ES snapshots API + */ +export const SNAPSHOT_LIST_MAX_SIZE = 1000; diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts index 07718c6d3d29f..de769686dc99c 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts @@ -12,10 +12,10 @@ describe('Snapshot serialization and deserialization', () => { test('deserializes a snapshot', () => { expect( deserializeSnapshotDetails( - 'repositoryName', { snapshot: 'snapshot name', uuid: 'UUID', + repository: 'repositoryName', version_id: 5, version: 'version', indices: ['index2', 'index3', 'index1'], @@ -55,6 +55,7 @@ describe('Snapshot serialization and deserialization', () => { { snapshot: 'last_successful_snapshot', uuid: 'last_successful_snapshot_UUID', + repository: 'repositoryName', version_id: 5, version: 'version', indices: ['index2', 'index3', 'index1'], diff --git a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts index f8d73b489dc38..f2803a571c475 100644 --- a/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts +++ b/x-pack/plugins/snapshot_restore/common/lib/snapshot_serialization.ts @@ -21,7 +21,6 @@ import { deserializeTime, serializeTime } from './time_serialization'; import { csvToArray } from './utils'; export function deserializeSnapshotDetails( - repository: string, snapshotDetailsEs: SnapshotDetailsEs, managedRepository?: string, successfulSnapshots?: SnapshotDetailsEs[] @@ -33,6 +32,7 @@ export function deserializeSnapshotDetails( const { snapshot, uuid, + repository, version_id: versionId, version, indices = [], diff --git a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts index fba55bec9fa97..97f3b00d97326 100644 --- a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts +++ b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts @@ -52,6 +52,7 @@ export interface SnapshotDetails { export interface SnapshotDetailsEs { snapshot: string; uuid: string; + repository: string; version_id: number; version: string; indices: string[]; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index f1c6b61e27c9c..92c03d1be936d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -19,7 +19,7 @@ import { EuiIcon, } from '@elastic/eui'; -import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common'; +import { APP_SLM_CLUSTER_PRIVILEGES, SNAPSHOT_LIST_MAX_SIZE } from '../../../../../common'; import { WithPrivileges, PageLoading, PageError, Error } from '../../../../shared_imports'; import { BASE_PATH, UIM_SNAPSHOT_LIST_LOAD } from '../../../constants'; import { useLoadSnapshots } from '../../../services/http'; @@ -54,7 +54,7 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara resendRequest: reload, } = useLoadSnapshots(); - const { uiMetricService } = useServices(); + const { uiMetricService, i18n } = useServices(); const { docLinks } = useCore(); const openSnapshotDetailsUrl = ( @@ -138,6 +138,7 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara <EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger"> <EuiEmptyPrompt iconType="managementApp" + data-test-subj="repositoryErrorsPrompt" title={ <h1 data-test-subj="title"> <FormattedMessage @@ -319,7 +320,7 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara ); } else { const repositoryErrorsWarning = Object.keys(errors).length ? ( - <Fragment> + <> <EuiCallOut title={ <FormattedMessage @@ -329,6 +330,7 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara } color="warning" iconType="alert" + data-test-subj="repositoryErrorsWarning" > <FormattedMessage id="xpack.snapshotRestore.repositoryWarningDescription" @@ -346,13 +348,48 @@ export const SnapshotList: React.FunctionComponent<RouteComponentProps<MatchPara /> </EuiCallOut> <EuiSpacer /> - </Fragment> + </> ) : null; + const maxSnapshotsWarning = snapshots.length === SNAPSHOT_LIST_MAX_SIZE && ( + <> + <EuiCallOut + color="warning" + iconType="help" + data-test-subj="maxSnapshotsWarning" + title={i18n.translate('xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedTitle', { + defaultMessage: 'Cannot show the full list of snapshots', + })} + > + <FormattedMessage + id="xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDescription" + defaultMessage="You've reached the maximum number of viewable snapshots. To view all of your snapshots, use {docLink}." + values={{ + docLink: ( + <EuiLink + href={docLinks.links.snapshotRestore.getSnapshot} + target="_blank" + data-test-subj="documentationLink" + > + <FormattedMessage + id="xpack.snapshotRestore.snapshotsList.maxSnapshotsDisplayedDocLinkText" + defaultMessage="the Elasticsearch API" + /> + </EuiLink> + ), + }} + /> + </EuiCallOut> + <EuiSpacer size="l" /> + </> + ); + content = ( <section data-test-subj="snapshotList"> {repositoryErrorsWarning} + {maxSnapshotsWarning} + <SnapshotTable snapshots={snapshots} repositories={repositories} diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts index 7d14d62bfe1a0..c3389e893407d 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts @@ -166,12 +166,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => { [name]: { type: '', settings: {} }, }; const mockEsSnapshotResponse = { - responses: [ - { - repository: name, - snapshots: [{}, {}], - }, - ], + snapshots: [{ repository: name }, { repository: name }], }; getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts index 4254562a0a886..4898c6e299ad3 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -116,7 +116,7 @@ export function registerRepositoriesRoutes({ snapshot: '_all', }); - const { responses: snapshotResponses } = response.body; + const { snapshots: snapshotList } = response.body; if (repositoryByName[name]) { const { type = '', settings = {} } = repositoryByName[name]; @@ -130,10 +130,7 @@ export function registerRepositoriesRoutes({ }, isManagedRepository: managedRepository === name, snapshots: { - count: - snapshotResponses && snapshotResponses[0] && snapshotResponses[0].snapshots - ? snapshotResponses[0].snapshots.length - : null, + count: snapshotList ? snapshotList.length : null, }, }, }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index bd7dffe987feb..00543d7081d34 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -36,7 +36,6 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { */ const getClusterSettingsFn = router.getMockApiFn('cluster.getSettings'); const getLifecycleFn = router.getMockApiFn('slm.getLifecycle'); - const getRepoFn = router.getMockApiFn('snapshot.getRepository'); const getSnapshotFn = router.getMockApiFn('snapshot.get'); const deleteSnapshotFn = router.getMockApiFn('snapshot.delete'); @@ -64,37 +63,18 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { fooPolicy: {}, }; - const mockSnapshotGetRepositoryEsResponse = { - fooRepository: {}, - barRepository: {}, - }; - - const mockGetSnapshotsFooResponse = { - responses: [ - { - repository: 'fooRepository', - snapshots: [{ snapshot: 'snapshot1' }], - }, - ], - }; - - const mockGetSnapshotsBarResponse = { - responses: [ - { - repository: 'barRepository', - snapshots: [{ snapshot: 'snapshot2' }], - }, + const mockGetSnapshotsResponse = { + snapshots: [ + { snapshot: 'snapshot1', repository: 'fooRepository' }, + { snapshot: 'snapshot2', repository: 'barRepository' }, ], }; getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); - getRepoFn.mockResolvedValue({ body: mockSnapshotGetRepositoryEsResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsFooResponse }); - getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsBarResponse }); + getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsResponse }); const expectedResponse = { - errors: {}, repositories: ['fooRepository', 'barRepository'], policies: ['fooPolicy'], snapshots: [ @@ -123,16 +103,62 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { expect(response).toEqual({ body: expectedResponse }); }); + test('returns an error object if ES request contains repository failures', async () => { + const mockSnapshotGetPolicyEsResponse = { + fooPolicy: {}, + }; + + const mockGetSnapshotsResponse = { + snapshots: [{ snapshot: 'snapshot1', repository: 'fooRepository' }], + failures: { + bar: { + type: 'repository_exception', + reason: + "[barRepository] Could not read repository data because the contents of the repository do not match its expected state. This is likely the result of either concurrently modifying the contents of the repository by a process other than this cluster or an issue with the repository's underlying storage. The repository has been disabled to prevent corrupting its contents. To re-enable it and continue using it please remove the repository from the cluster and add it again to make the cluster recover the known state of the repository from its physical contents.", + }, + }, + }; + + getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); + getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); + getSnapshotFn.mockResolvedValueOnce({ body: mockGetSnapshotsResponse }); + + const expectedResponse = { + repositories: ['fooRepository'], + policies: ['fooPolicy'], + snapshots: [ + { + ...defaultSnapshot, + repository: 'fooRepository', + snapshot: 'snapshot1', + managedRepository: + mockSnapshotGetManagedRepositoryEsResponse.defaults[ + 'cluster.metadata.managed_repository' + ], + }, + ], + errors: { + bar: { + type: 'repository_exception', + reason: + "[barRepository] Could not read repository data because the contents of the repository do not match its expected state. This is likely the result of either concurrently modifying the contents of the repository by a process other than this cluster or an issue with the repository's underlying storage. The repository has been disabled to prevent corrupting its contents. To re-enable it and continue using it please remove the repository from the cluster and add it again to make the cluster recover the known state of the repository from its physical contents.", + }, + }, + }; + + const response = await router.runRequest(mockRequest); + expect(response).toEqual({ body: expectedResponse }); + }); + test('returns empty arrays if no snapshots returned from ES', async () => { const mockSnapshotGetPolicyEsResponse = {}; const mockSnapshotGetRepositoryEsResponse = {}; getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); getLifecycleFn.mockResolvedValue({ body: mockSnapshotGetPolicyEsResponse }); - getRepoFn.mockResolvedValue({ body: mockSnapshotGetRepositoryEsResponse }); + getSnapshotFn.mockResolvedValue({ body: mockSnapshotGetRepositoryEsResponse }); const expectedResponse = { - errors: [], snapshots: [], repositories: [], policies: [], @@ -145,7 +171,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { test('throws if ES error', async () => { getClusterSettingsFn.mockRejectedValueOnce(new Error()); getLifecycleFn.mockRejectedValueOnce(new Error()); - getRepoFn.mockRejectedValueOnce(new Error()); + getSnapshotFn.mockRejectedValueOnce(new Error()); await expect(router.runRequest(mockRequest)).rejects.toThrowError(); }); @@ -172,12 +198,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { test('returns snapshot object with repository name if returned from ES', async () => { const mockSnapshotGetEsResponse = { - responses: [ - { - repository, - snapshots: [{ snapshot }], - }, - ], + snapshots: [{ snapshot, repository }], }; getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index af9c08f76f6f7..7307bad947211 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -6,7 +6,8 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import type { SnapshotDetails, SnapshotDetailsEs } from '../../../common/types'; +import type { SnapshotDetailsEs } from '../../../common/types'; +import { SNAPSHOT_LIST_MAX_SIZE } from '../../../common/constants'; import { deserializeSnapshotDetails } from '../../../common/lib'; import type { RouteDependencies } from '../../types'; import { getManagedRepositoryName } from '../../lib'; @@ -36,76 +37,45 @@ export function registerSnapshotsRoutes({ // Silently swallow error as policy names aren't required in UI } - /* - * TODO: For 8.0, replace the logic in this handler with one call to `GET /_snapshot/_all/_all` - * when no repositories bug is fixed: https://github.com/elastic/elasticsearch/issues/43547 - */ - - let repositoryNames: string[]; - try { - const { - body: repositoriesByName, - } = await clusterClient.asCurrentUser.snapshot.getRepository({ + // If any of these repositories 504 they will cost the request significant time. + const { body: fetchedSnapshots } = await clusterClient.asCurrentUser.snapshot.get({ repository: '_all', + snapshot: '_all', + ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable. + // @ts-expect-error @elastic/elasticsearch "desc" is a new param + order: 'desc', + // TODO We are temporarily hard-coding the maximum number of snapshots returned + // in order to prevent an unusable UI for users with large number of snapshots + // In the near future, this will be resolved with server-side pagination + size: SNAPSHOT_LIST_MAX_SIZE, }); - repositoryNames = Object.keys(repositoriesByName); - if (repositoryNames.length === 0) { - return res.ok({ - body: { snapshots: [], errors: [], repositories: [], policies }, - }); - } - } catch (e) { - return handleEsError({ error: e, response: res }); - } + const allRepos: string[] = []; - const snapshots: SnapshotDetails[] = []; - const errors: any = {}; - const repositories: string[] = []; - - const fetchSnapshotsForRepository = async (repository: string) => { - try { - // If any of these repositories 504 they will cost the request significant time. - const response = await clusterClient.asCurrentUser.snapshot.get({ - repository, - snapshot: '_all', - ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable. - }); - - const { responses: fetchedResponses = [] } = response.body; - - // Decorate each snapshot with the repository with which it's associated. - fetchedResponses.forEach(({ snapshots: fetchedSnapshots = [] }) => { - fetchedSnapshots.forEach((snapshot) => { - snapshots.push( - deserializeSnapshotDetails( - repository, - snapshot as SnapshotDetailsEs, - managedRepository - ) - ); - }); - }); - - repositories.push(repository); - } catch (error) { - // These errors are commonly due to a misconfiguration in the repository or plugin errors, - // which can result in a variety of 400, 404, and 500 errors. - errors[repository] = error; - } - }; + // Decorate each snapshot with the repository with which it's associated. + const snapshots = fetchedSnapshots?.snapshots?.map((snapshot) => { + // @ts-expect-error @elastic/elasticsearch "repository" is a new field in the response + allRepos.push(snapshot.repository); + return deserializeSnapshotDetails(snapshot as SnapshotDetailsEs, managedRepository); + }); - await Promise.all(repositoryNames.map(fetchSnapshotsForRepository)); + const uniqueRepos = allRepos.filter((repo, index) => { + return allRepos.indexOf(repo) === index; + }); - return res.ok({ - body: { - snapshots, - policies, - repositories, - errors, - }, - }); + return res.ok({ + body: { + snapshots: snapshots || [], + policies, + repositories: uniqueRepos, + // @ts-expect-error @elastic/elasticsearch "failures" is a new field in the response + errors: fetchedSnapshots?.failures, + }, + }); + } catch (e) { + return handleEsError({ error: e, response: res }); + } }) ); @@ -132,13 +102,12 @@ export function registerSnapshotsRoutes({ ignore_unavailable: true, }); - const { responses: snapshotsResponse } = response.body; + const { snapshots: snapshotsList } = response.body; - const snapshotsList = - snapshotsResponse && snapshotsResponse[0] && snapshotsResponse[0].snapshots; if (!snapshotsList || snapshotsList.length === 0) { return res.notFound({ body: 'Snapshot not found' }); } + const selectedSnapshot = snapshotsList.find( ({ snapshot: snapshotName }) => snapshot === snapshotName ) as SnapshotDetailsEs; @@ -156,7 +125,6 @@ export function registerSnapshotsRoutes({ return res.ok({ body: deserializeSnapshotDetails( - repository, selectedSnapshot, managedRepository, successfulSnapshots diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index db6ec3ad7dd5b..c76a762d96227 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -21755,9 +21755,6 @@ "xpack.snapshotRestore.repositoryVerification.verificationErrorValue": "未接続", "xpack.snapshotRestore.repositoryVerification.verificationSuccessfulValue": "接続済み", "xpack.snapshotRestore.repositoryVerification.verificationUnknownValue": "不明", - "xpack.snapshotRestore.repositoryWarningDescription": "スナップショットの読み込みが遅い可能性があります。{repositoryLink} に移動してエラーを解決してください。", - "xpack.snapshotRestore.repositoryWarningLinkText": "レポジトリ", - "xpack.snapshotRestore.repositoryWarningTitle": "一部のレポジトリにエラーがあります", "xpack.snapshotRestore.restoreForm.backButtonLabel": "戻る", "xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body": "各データストリームには、一致するインデックステンプレートが必要です。復元されたすべてのデータストリームに一致するインデックステンプレートがあることを確認してください。インデックステンプレートを復元するには、グローバルクラスター状態を復元します。ただし、既存のテンプレート、クラスター設定、入力パイプライン、ライフサイクルポリシーが上書きされる場合があります。データストリームを含むスナップショットの復元については、{learnMoreLink}。", "xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body.learnMoreLink": "詳細", @@ -21907,14 +21904,12 @@ "xpack.snapshotRestore.snapshotDetails.snapshotIsBeingCreatedMessage": "スナップショットを作成中です。", "xpack.snapshotRestore.snapshotDetails.summaryTabTitle": "まとめ", "xpack.snapshotRestore.snapshotList.emptyPrompt.addPolicyText": "ポリシーを作成", - "xpack.snapshotRestore.snapshotList.emptyPrompt.errorRepositoriesTitle": "一部のレポジトリにエラーがあります", "xpack.snapshotRestore.snapshotList.emptyPrompt.goToPoliciesText": "ポリシーを表示", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesAddButtonLabel": "レポジトリを登録", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesDescription": "スナップショットがライブである場所が必要です。", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesTitle": "リポジトリを登録して始める", "xpack.snapshotRestore.snapshotList.emptyPrompt.noSnapshotsDescription": "Elasticsearch API でスナップショットを作成します。", "xpack.snapshotRestore.snapshotList.emptyPrompt.noSnapshotsTitle": "まだスナップショットがありません", - "xpack.snapshotRestore.snapshotList.emptyPrompt.repositoryWarningDescription": "{repositoryLink} に移動してエラーを解決してください。", "xpack.snapshotRestore.snapshotList.emptyPrompt.usePolicyDescription": "スナップショットを作成するには、スナップショットライフサイクルポリシーを実行してください。スナップショットは {docLink} でも作成できます。", "xpack.snapshotRestore.snapshotList.loadingSnapshotsDescription": "スナップショットを読み込み中…", "xpack.snapshotRestore.snapshotList.loadingSnapshotsErrorMessage": "スナップショットの読み込み中にエラーが発生しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 790f41209bd28..16a63682facde 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22101,9 +22101,6 @@ "xpack.snapshotRestore.repositoryVerification.verificationErrorValue": "未连接", "xpack.snapshotRestore.repositoryVerification.verificationSuccessfulValue": "已连接", "xpack.snapshotRestore.repositoryVerification.verificationUnknownValue": "未知", - "xpack.snapshotRestore.repositoryWarningDescription": "快照可能加载缓慢。前往 {repositoryLink} 以修复错误。", - "xpack.snapshotRestore.repositoryWarningLinkText": "存储库", - "xpack.snapshotRestore.repositoryWarningTitle": "一些存储库包含错误", "xpack.snapshotRestore.restoreForm.backButtonLabel": "返回", "xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body": "每个数据流需要匹配的索引模板。请确保任何存储的数据流有匹配的索引模板。可以通过存储全局集群状态来存储索引模板。不过,这可能会覆盖现有模板、集群设置、采集管道和生命周期策略。{learnMoreLink}如何存储包含数据流的快照。", "xpack.snapshotRestore.restoreForm.dataStreamsWarningCallOut.body.learnMoreLink": "了解详情", @@ -22259,14 +22256,12 @@ "xpack.snapshotRestore.snapshotDetails.snapshotIsBeingCreatedMessage": "正在创建快照。", "xpack.snapshotRestore.snapshotDetails.summaryTabTitle": "摘要", "xpack.snapshotRestore.snapshotList.emptyPrompt.addPolicyText": "创建策略", - "xpack.snapshotRestore.snapshotList.emptyPrompt.errorRepositoriesTitle": "一些存储库包含错误", "xpack.snapshotRestore.snapshotList.emptyPrompt.goToPoliciesText": "查看策略", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesAddButtonLabel": "注册存储库", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesDescription": "您需要将用于安置快照的位置。", "xpack.snapshotRestore.snapshotList.emptyPrompt.noRepositoriesTitle": "首先注册存储库", "xpack.snapshotRestore.snapshotList.emptyPrompt.noSnapshotsDescription": "使用 Elasticsearch API 创建快照", "xpack.snapshotRestore.snapshotList.emptyPrompt.noSnapshotsTitle": "您尚未有任何快照", - "xpack.snapshotRestore.snapshotList.emptyPrompt.repositoryWarningDescription": "前往 {repositoryLink} 以修复错误。", "xpack.snapshotRestore.snapshotList.emptyPrompt.usePolicyDescription": "运行快照生命周期策略以创建快照。还可以使用 {docLink} 创建快照。", "xpack.snapshotRestore.snapshotList.loadingSnapshotsDescription": "正在加载快照……", "xpack.snapshotRestore.snapshotList.loadingSnapshotsErrorMessage": "加载快照时出错", From 46402538d2771b2a448e1c9a71e306e72af86fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= <ester.martivilaseca@elastic.co> Date: Tue, 29 Jun 2021 17:35:37 +0200 Subject: [PATCH 74/74] [Monitoring] Enable out of the box alerts modal (#101565) * Remove api call to create alerts * Add enable alerts modal * Update modal title * Add simple alerts dropdown * change alerts modal design * refactor alerts modal provider * Add alerts dropdown * Show toast after alert creation and add error handling * Do not show alerts modal if alerts already exist * Fix stack monitoring test * Fix more stack monitoring tests and types * Fix tests after merge * Attempt to fix stack monitoring tests * remove console.log * Change text * Remove commented comment * Update docs for stack monitoring alerts * Fix docs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/user/monitoring/kibana-alerts.asciidoc | 12 +- .../public/alerts/alerts_dropdown.tsx | 77 +++++++++ .../public/alerts/enable_alerts_modal.tsx | 148 ++++++++++++++++++ .../monitoring/public/angular/app_modules.ts | 5 + .../monitoring/public/angular/index.ts | 2 + .../plugins/monitoring/public/legacy_shims.ts | 13 +- x-pack/plugins/monitoring/public/plugin.ts | 1 + .../monitoring/public/services/clusters.js | 14 +- .../public/services/enable_alerts_modal.js | 53 +++++++ x-pack/plugins/monitoring/public/types.ts | 3 +- .../public/views/base_controller.js | 7 + .../public/views/cluster/overview/index.js | 2 + .../apps/monitoring/beats/beat_detail.js | 2 + .../apps/monitoring/beats/cluster.js | 2 + .../apps/monitoring/beats/listing.js | 2 + .../apps/monitoring/beats/overview.js | 2 + .../apps/monitoring/cluster/list.js | 1 + .../apps/monitoring/cluster/overview.js | 6 + .../monitoring/elasticsearch/index_detail.js | 2 + .../elasticsearch/index_detail_mb.js | 2 + .../apps/monitoring/elasticsearch/indices.js | 2 + .../monitoring/elasticsearch/indices_mb.js | 2 + .../monitoring/elasticsearch/node_detail.js | 6 + .../elasticsearch/node_detail_mb.js | 6 + .../apps/monitoring/elasticsearch/nodes.js | 2 + .../apps/monitoring/elasticsearch/nodes_mb.js | 4 + .../apps/monitoring/elasticsearch/overview.js | 2 + .../monitoring/elasticsearch/overview_mb.js | 2 + .../monitoring/enable_monitoring/index.js | 1 + .../apps/monitoring/kibana/instance.js | 2 + .../apps/monitoring/kibana/instance_mb.js | 2 + .../apps/monitoring/kibana/instances.js | 2 + .../apps/monitoring/kibana/instances_mb.js | 2 + .../apps/monitoring/kibana/overview.js | 2 + .../apps/monitoring/kibana/overview_mb.js | 2 + .../apps/monitoring/logstash/pipelines.js | 2 + .../apps/monitoring/logstash/pipelines_mb.js | 2 + .../page_objects/monitoring_page.ts | 4 + .../services/monitoring/cluster_overview.js | 4 + 39 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx create mode 100644 x-pack/plugins/monitoring/public/services/enable_alerts_modal.js diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index ccd023f180c99..67e4520f5c70c 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -11,8 +11,8 @@ specific needs. [role="screenshot"] image::user/monitoring/images/monitoring-kibana-alerting-notification.png["{kib} alerting notifications in {stack-monitor-app}"] -When you open *{stack-monitor-app}*, the preconfigured rules are created -automatically. They are initially configured to detect and notify on various +When you open *{stack-monitor-app}*, you will be ask to create these rules +They are initially configured to detect and notify on various conditions across your monitored clusters. You can view notifications for: *Cluster health*, *Resource utilization*, and *Errors and exceptions* for {es} in real time. @@ -131,6 +131,14 @@ soon the expiration date is: The 60-day and 30-day thresholds are skipped for Trial licenses, which are only valid for 30 days. +[discrete] +== Alerts and rules +[discrete] +=== Create default rules +This option can be used to create default rules in this kibana spaces. This is +useful for scenarios when you didn't choose to create these default rules initially +or anytime later if the rules were accidentally deleted. + NOTE: Some action types are subscription features, while others are free. For a comparison of the Elastic subscription levels, see the alerting section of the {subscriptions}[Subscriptions page]. diff --git a/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx b/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx new file mode 100644 index 0000000000000..df0cbb43f8569 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonEmpty, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiPopover, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Legacy } from '../legacy_shims'; + +export const AlertsDropdown: React.FC<{}> = () => { + const $injector = Legacy.shims.getAngularInjector(); + const alertsEnableModalProvider: any = $injector.get('enableAlertsModal'); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const closePopover = () => { + alertsEnableModalProvider.enableAlerts(); + setIsPopoverOpen(false); + }; + + const togglePopoverVisibility = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const createDefaultRules = () => { + closePopover(); + }; + + const button = ( + <EuiButtonEmpty iconSide={'right'} iconType={'arrowDown'} onClick={togglePopoverVisibility}> + <FormattedMessage + id="xpack.monitoring.alerts.dropdown.button" + defaultMessage="Alerts and rules" + /> + </EuiButtonEmpty> + ); + + const items = [ + { + name: i18n.translate('xpack.monitoring.alerts.dropdown.createAlerts', { + defaultMessage: 'Create default rules', + }), + onClick: createDefaultRules, + }, + ]; + + const panels: EuiContextMenuPanelDescriptor[] = [ + { + id: 0, + title: i18n.translate('xpack.monitoring.alerts.dropdown.title', { + defaultMessage: 'Alerts and rules', + }), + items, + }, + ]; + + return ( + <EuiPopover + panelPaddingSize="none" + anchorPosition="downLeft" + button={button} + isOpen={isPopoverOpen} + closePopover={closePopover} + > + <EuiContextMenu initialPanelId={0} panels={panels} /> + </EuiPopover> + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx new file mode 100644 index 0000000000000..914446c42aaa7 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState, useContext } from 'react'; + +import { + EuiButton, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiButtonEmpty, + EuiText, + EuiLink, + EuiRadioGroup, + EuiSpacer, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { AlertsContext } from './context'; +import { Legacy } from '../legacy_shims'; + +export const EnableAlertsModal: React.FC<{}> = () => { + const [isModalVisible, setIsModalVisible] = useState(false); + const $injector = Legacy.shims.getAngularInjector(); + const alertsEnableModalProvider: any = $injector.get('enableAlertsModal'); + const alertsContext = useContext(AlertsContext); + + const closeModal = () => { + setIsModalVisible(false); + alertsEnableModalProvider.hideModalForSession(); + }; + + const radios = [ + { + id: 'create-alerts', + label: i18n.translate('xpack.monitoring.alerts.modal.yesOption', { + defaultMessage: 'Yes (Recommended - create default rules in this kibana spaces)', + }), + }, + { + id: 'not-create-alerts', + label: i18n.translate('xpack.monitoring.alerts.modal.noOption', { + defaultMessage: 'No', + }), + }, + ]; + + const [radioIdSelected, setRadioIdSelected] = useState('create-alerts'); + + const onChange = (optionId: string) => { + setRadioIdSelected(optionId); + }; + + useEffect(() => { + if (alertsEnableModalProvider.shouldShowAlertsModal(alertsContext)) { + setIsModalVisible(true); + } + }, [alertsEnableModalProvider, alertsContext]); + + const confirmButtonClick = () => { + if (radioIdSelected === 'create-alerts') { + alertsEnableModalProvider.enableAlerts(); + } else { + alertsEnableModalProvider.notAskAgain(); + } + + closeModal(); + }; + + const remindLaterClick = () => { + alertsEnableModalProvider.hideModalForSession(); + closeModal(); + }; + + return isModalVisible ? ( + <EuiModal onClose={closeModal}> + <EuiModalHeader> + <EuiModalHeaderTitle> + <h1> + <FormattedMessage + id="xpack.monitoring.alerts.modal.title" + defaultMessage="Create rules" + /> + </h1> + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <EuiText> + <p> + <FormattedMessage + id="xpack.monitoring.alerts.modal.description" + defaultMessage="Stack monitoring comes with many out-of-the box rules to notify you of common issues + around cluster health, resource utilization and errors or exceptions. {learnMoreLink}" + values={{ + learnMoreLink: ( + <EuiLink + href={Legacy.shims.docLinks.links.monitoring.alertsKibana} + target="_blank" + > + <FormattedMessage + id="xpack.monitoring.alerts.modal.description.link" + defaultMessage="Learn more..." + /> + </EuiLink> + ), + }} + /> + </p> + <div> + <FormattedMessage + id="xpack.monitoring.alerts.modal.createDescription" + defaultMessage="Create these out-of-the box rules?" + /> + + <EuiSpacer size="xs" /> + + <EuiRadioGroup + options={radios} + idSelected={radioIdSelected} + onChange={(id) => onChange(id)} + name="radio group" + /> + </div> + </EuiText> + </EuiModalBody> + + <EuiModalFooter> + <EuiButtonEmpty onClick={remindLaterClick}> + <FormattedMessage + id="xpack.monitoring.alerts.modal.remindLater" + defaultMessage="Remind me later" + /> + </EuiButtonEmpty> + + <EuiButton onClick={confirmButtonClick} fill data-test-subj="alerts-modal-button"> + <FormattedMessage id="xpack.monitoring.alerts.modal.confirm" defaultMessage="Ok" /> + </EuiButton> + </EuiModalFooter> + </EuiModal> + ) : null; +}; diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 71dc4919237e5..e7b2f7a537000 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -42,6 +42,8 @@ import { licenseProvider } from '../services/license'; // @ts-ignore import { titleProvider } from '../services/title'; // @ts-ignore +import { enableAlertsModalProvider } from '../services/enable_alerts_modal'; +// @ts-ignore import { monitoringMlListingProvider } from '../directives/elasticsearch/ml_job_listing'; // @ts-ignore import { monitoringMainProvider } from '../directives/main'; @@ -142,6 +144,9 @@ function createMonitoringAppServices() { .service('features', function (Private: IPrivate) { return Private(featuresProvider); }) + .service('enableAlertsModal', function (Private: IPrivate) { + return Private(enableAlertsModalProvider); + }) .service('license', function (Private: IPrivate) { return Private(licenseProvider); }) diff --git a/x-pack/plugins/monitoring/public/angular/index.ts b/x-pack/plugins/monitoring/public/angular/index.ts index 0c605abb828bd..de52b714a7241 100644 --- a/x-pack/plugins/monitoring/public/angular/index.ts +++ b/x-pack/plugins/monitoring/public/angular/index.ts @@ -29,6 +29,7 @@ export class AngularApp { triggersActionsUi, usageCollection, kibanaLegacy, + appMountParameters, } = deps; const app: IModule = localAppModule(deps); app.run(($injector: angular.auto.IInjectorService) => { @@ -45,6 +46,7 @@ export class AngularApp { kibanaLegacy, triggersActionsUi, usageCollection, + appMountParameters, }, this.injector ); diff --git a/x-pack/plugins/monitoring/public/legacy_shims.ts b/x-pack/plugins/monitoring/public/legacy_shims.ts index 7da0f8590a896..a723eea8b6d65 100644 --- a/x-pack/plugins/monitoring/public/legacy_shims.ts +++ b/x-pack/plugins/monitoring/public/legacy_shims.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CoreStart, HttpSetup, IUiSettingsClient } from 'kibana/public'; +import { CoreStart, HttpSetup, IUiSettingsClient, AppMountParameters } from 'kibana/public'; import { Observable } from 'rxjs'; import { HttpRequestInit } from '../../../../src/core/public'; import { MonitoringStartPluginDependencies } from './types'; @@ -63,13 +63,21 @@ export interface IShims { triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; kibanaServices: CoreStart & { usageCollection: UsageCollectionSetup }; + appMountParameters: AppMountParameters; } export class Legacy { private static _shims: IShims; public static init( - { core, data, isCloud, triggersActionsUi, usageCollection }: MonitoringStartPluginDependencies, + { + core, + data, + isCloud, + triggersActionsUi, + usageCollection, + appMountParameters, + }: MonitoringStartPluginDependencies, ngInjector: angular.auto.IInjectorService ) { this._shims = { @@ -129,6 +137,7 @@ export class Legacy { ...core, usageCollection, }, + appMountParameters, }; } diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index a597754d6c409..a5b7d4906b586 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -105,6 +105,7 @@ export class MonitoringPlugin externalConfig: this.getExternalConfig(), triggersActionsUi: pluginsStart.triggersActionsUi, usageCollection: plugins.usageCollection, + appMountParameters: params, }; const monitoringApp = new AngularApp(deps); diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js index 71ae128072b7f..937a69cbbc32d 100644 --- a/x-pack/plugins/monitoring/public/services/clusters.js +++ b/x-pack/plugins/monitoring/public/services/clusters.js @@ -9,7 +9,6 @@ import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler'; import { Legacy } from '../legacy_shims'; import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants'; import { showInternalMonitoringToast } from '../lib/internal_monitoring_toasts'; -import { showAlertsToast } from '../alerts/lib/alerts_toast'; function formatClusters(clusters) { return clusters.map(formatCluster); @@ -58,16 +57,6 @@ export function monitoringClustersProvider($injector) { } } - async function ensureAlertsEnabled() { - try { - return $http.post('../api/monitoring/v1/alerts/enable', {}); - } catch (err) { - const Private = $injector.get('Private'); - const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); - return ajaxErrorHandlers(err); - } - } - async function ensureMetricbeatEnabled() { if (Legacy.shims.isCloud) { return; @@ -97,8 +86,7 @@ export function monitoringClustersProvider($injector) { const clusters = await getClusters(); if (clusters.length) { try { - const [{ data }] = await Promise.all([ensureAlertsEnabled(), ensureMetricbeatEnabled()]); - showAlertsToast(data); + await ensureMetricbeatEnabled(); } catch (_err) { // Intentionally swallow the error as this will retry the next page load } diff --git a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js new file mode 100644 index 0000000000000..0232e302517af --- /dev/null +++ b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler'; +import { showAlertsToast } from '../alerts/lib/alerts_toast'; + +export function enableAlertsModalProvider($http, $window, $injector) { + function shouldShowAlertsModal(alerts) { + const modalHasBeenShown = $window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN'); + const decisionMade = $window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE'); + + if (Object.keys(alerts.allAlerts).length > 0) { + $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true); + return false; + } + + if (!modalHasBeenShown && !decisionMade) { + return true; + } + + return false; + } + + async function enableAlerts() { + try { + const { data } = await $http.post('../api/monitoring/v1/alerts/enable', {}); + $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true); + showAlertsToast(data); + } catch (err) { + const Private = $injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + } + } + + function notAskAgain() { + $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true); + } + + function hideModalForSession() { + $window.sessionStorage.setItem('ALERTS_MODAL_HAS_BEEN_SHOWN', true); + } + + return { + shouldShowAlertsModal, + enableAlerts, + notAskAgain, + hideModalForSession, + }; +} diff --git a/x-pack/plugins/monitoring/public/types.ts b/x-pack/plugins/monitoring/public/types.ts index f722a08be3866..da65a3a3d64f1 100644 --- a/x-pack/plugins/monitoring/public/types.ts +++ b/x-pack/plugins/monitoring/public/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PluginInitializerContext, CoreStart } from 'kibana/public'; +import { PluginInitializerContext, CoreStart, AppMountParameters } from 'kibana/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { TriggersAndActionsUIPublicPluginStart } from '../../triggers_actions_ui/public'; @@ -26,4 +26,5 @@ export interface MonitoringStartPluginDependencies { externalConfig: Array<Array<string | number> | Array<string | boolean>>; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; + appMountParameters: AppMountParameters; } diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index ce541a1806e55..dd9898a6e195c 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -16,6 +16,8 @@ import { SetupModeFeature } from '../../common/enums'; import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode'; import { AlertsContext } from '../alerts/context'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { AlertsDropdown } from '../alerts/alerts_dropdown'; +import { HeaderMenuPortal } from '../../../observability/public'; /** * Given a timezone, this function will calculate the offset in milliseconds @@ -246,6 +248,11 @@ export class MonitoringViewBaseController { <KibanaContextProvider services={Legacy.shims.kibanaServices}> <I18nContext> <AlertsContext.Provider value={{ allAlerts: this.alerts }}> + <HeaderMenuPortal + setHeaderActionMenu={Legacy.shims.appMountParameters.setHeaderActionMenu} + > + <AlertsDropdown /> + </HeaderMenuPortal> {!this._isDataInitialized ? ( <PageLoading pageViewTitle={trackPageView ? this.telemetryPageViewTitle : null} /> ) : ( diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js index 9fc9dd1c6f685..bf34650bdb700 100644 --- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js @@ -16,6 +16,7 @@ import { Overview } from '../../../components/cluster/overview'; import { SetupModeRenderer } from '../../../components/renderers'; import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; import { CODE_PATH_ALL } from '../../../../common/constants'; +import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx'; const CODE_PATHS = [CODE_PATH_ALL]; @@ -82,6 +83,7 @@ uiRoutes.when('/overview', { setupMode={setupMode} showLicenseExpiration={showLicenseExpiration} /> + <EnableAlertsModal /> {bottomBarComponent} </SetupModeContext.Provider> )} diff --git a/x-pack/test/functional/apps/monitoring/beats/beat_detail.js b/x-pack/test/functional/apps/monitoring/beats/beat_detail.js index f3d89ebdeddbd..35a74a0877aa5 100644 --- a/x-pack/test/functional/apps/monitoring/beats/beat_detail.js +++ b/x-pack/test/functional/apps/monitoring/beats/beat_detail.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Dec 19, 2017 @ 18:15:09.000', }); + await clusterOverview.closeAlertsModal(); + // go to beats detail await clusterOverview.clickBeatsListing(); expect(await listing.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/beats/cluster.js b/x-pack/test/functional/apps/monitoring/beats/cluster.js index 7f8f1b181b724..9d291814ae19c 100644 --- a/x-pack/test/functional/apps/monitoring/beats/cluster.js +++ b/x-pack/test/functional/apps/monitoring/beats/cluster.js @@ -19,6 +19,8 @@ export default function ({ getService, getPageObjects }) { from: 'Dec 19, 2017 @ 17:14:09.000', to: 'Dec 19, 2017 @ 18:15:09.000', }); + + await overview.closeAlertsModal(); }); after(async () => { diff --git a/x-pack/test/functional/apps/monitoring/beats/listing.js b/x-pack/test/functional/apps/monitoring/beats/listing.js index 4030f80f5b8b9..67dc9181bda39 100644 --- a/x-pack/test/functional/apps/monitoring/beats/listing.js +++ b/x-pack/test/functional/apps/monitoring/beats/listing.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Dec 19, 2017 @ 18:15:09.000', }); + await clusterOverview.closeAlertsModal(); + // go to beats listing await clusterOverview.clickBeatsListing(); expect(await listing.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/beats/overview.js b/x-pack/test/functional/apps/monitoring/beats/overview.js index cf544f5e659fd..7d2eaa0689b23 100644 --- a/x-pack/test/functional/apps/monitoring/beats/overview.js +++ b/x-pack/test/functional/apps/monitoring/beats/overview.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Dec 19, 2017 @ 18:15:09.000', }); + await clusterOverview.closeAlertsModal(); + // go to beats overview await clusterOverview.clickBeatsOverview(); expect(await overview.isOnOverview()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/cluster/list.js b/x-pack/test/functional/apps/monitoring/cluster/list.js index 98ea3959d6b29..f88e30f717141 100644 --- a/x-pack/test/functional/apps/monitoring/cluster/list.js +++ b/x-pack/test/functional/apps/monitoring/cluster/list.js @@ -140,6 +140,7 @@ export default function ({ getService, getPageObjects }) { expect(await clusterOverview.isOnClusterOverview()).to.be(true); expect(await clusterOverview.getClusterName()).to.be('production'); + await PageObjects.monitoring.closeAlertsModal(); await PageObjects.monitoring.clickBreadcrumb('~breadcrumbClusters'); // reset for next test }); diff --git a/x-pack/test/functional/apps/monitoring/cluster/overview.js b/x-pack/test/functional/apps/monitoring/cluster/overview.js index 389c3313d2954..902c82f088152 100644 --- a/x-pack/test/functional/apps/monitoring/cluster/overview.js +++ b/x-pack/test/functional/apps/monitoring/cluster/overview.js @@ -20,6 +20,8 @@ export default function ({ getService, getPageObjects }) { from: 'Aug 23, 2017 @ 21:29:35.267', to: 'Aug 23, 2017 @ 21:47:25.556', }); + + await overview.closeAlertsModal(); }); after(async () => { @@ -71,6 +73,8 @@ export default function ({ getService, getPageObjects }) { from: 'Aug 29, 2017 @ 17:23:47.528', to: 'Aug 29, 2017 @ 17:25:50.701', }); + + await overview.closeAlertsModal(); }); after(async () => { @@ -117,6 +121,8 @@ export default function ({ getService, getPageObjects }) { from: 'Aug 29, 2017 @ 17:55:43.879', to: 'Aug 29, 2017 @ 18:01:34.958', }); + + await overview.closeAlertsModal(); }); after(async () => { diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail.js b/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail.js index 5ea7904e039a1..663b05442ba24 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail.js @@ -32,6 +32,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to indices listing await overview.clickEsIndices(); expect(await indicesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail_mb.js index 9d6938fb09c53..61a84cd60fbe0 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/index_detail_mb.js @@ -32,6 +32,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to indices listing await overview.clickEsIndices(); expect(await indicesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js b/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js index 44ded151f0713..ae35f53350ec9 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Oct 6, 2017 @ 20:15:30.212', }); + await overview.closeAlertsModal(); + // go to indices listing await overview.clickEsIndices(); expect(await indicesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/indices_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/indices_mb.js index 551afbc71b725..b0c53839fab36 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/indices_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/indices_mb.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Oct 6, 2017 @ 20:15:30.212', }); + await overview.closeAlertsModal(); + // go to indices listing await overview.clickEsIndices(); expect(await indicesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail.js b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail.js index 79fb399db8a4e..6b1658dd9ed0e 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail.js @@ -27,6 +27,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); @@ -82,6 +84,8 @@ export default function ({ getService, getPageObjects }) { to: 'Oct 6, 2017 @ 20:15:30.212', }); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); @@ -121,6 +125,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js index 342668533e963..9130ce91e7b4d 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js @@ -27,6 +27,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); @@ -82,6 +84,8 @@ export default function ({ getService, getPageObjects }) { to: 'Oct 6, 2017 @ 20:15:30.212', }); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); @@ -121,6 +125,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js index 9c03faf72eff1..d7c4e5dd12f52 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js @@ -29,6 +29,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes_mb.js index 78462e0d5ec3d..7e9a0fec70824 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes_mb.js @@ -29,6 +29,8 @@ export default function ({ getService, getPageObjects }) { } ); + await overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); @@ -261,6 +263,8 @@ export default function ({ getService, getPageObjects }) { } ); + overview.closeAlertsModal(); + // go to nodes listing await overview.clickEsNodes(); expect(await nodesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js b/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js index 61c8c6b266b0d..c539ca0b2623b 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js @@ -25,6 +25,8 @@ export default function ({ getService, getPageObjects }) { } ); + await clusterOverview.closeAlertsModal(); + // go to overview await clusterOverview.clickEsOverview(); expect(await overview.isOnOverview()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/overview_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/overview_mb.js index 7c279119e65d7..d93a2c3e77b93 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/overview_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/overview_mb.js @@ -25,6 +25,8 @@ export default function ({ getService, getPageObjects }) { } ); + await clusterOverview.closeAlertsModal(); + // go to overview await clusterOverview.clickEsOverview(); expect(await overview.isOnOverview()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js index dca81fd6fc07e..79bd479c45a17 100644 --- a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js @@ -55,6 +55,7 @@ export default function ({ getService, getPageObjects }) { await retry.tryForTime(20000, async () => { // Click the refresh button await testSubjects.click('querySubmitButton'); + await clusterOverview.closeAlertsModal(); expect(await clusterOverview.isOnClusterOverview()).to.be(true); }); }); diff --git a/x-pack/test/functional/apps/monitoring/kibana/instance.js b/x-pack/test/functional/apps/monitoring/kibana/instance.js index 97f73795e44e0..112642bff9b10 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/instance.js +++ b/x-pack/test/functional/apps/monitoring/kibana/instance.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Aug 29, 2017 @ 17:25:44.142', }); + await clusterOverview.closeAlertsModal(); + // go to kibana instance await clusterOverview.clickKibanaInstances(); expect(await instances.isOnInstances()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/kibana/instance_mb.js b/x-pack/test/functional/apps/monitoring/kibana/instance_mb.js index a615ed0cf9d59..0d993add1020a 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/instance_mb.js +++ b/x-pack/test/functional/apps/monitoring/kibana/instance_mb.js @@ -25,6 +25,8 @@ export default function ({ getService, getPageObjects }) { } ); + await clusterOverview.closeAlertsModal(); + // go to kibana instance await clusterOverview.clickKibanaInstances(); expect(await instances.isOnInstances()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/kibana/instances.js b/x-pack/test/functional/apps/monitoring/kibana/instances.js index 554c3c3ece0e1..f2c403578f52a 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/instances.js +++ b/x-pack/test/functional/apps/monitoring/kibana/instances.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Aug 29, 2017 @ 17:25:44.142', }); + await clusterOverview.closeAlertsModal(); + // go to kibana instances await clusterOverview.clickKibanaInstances(); expect(await instances.isOnInstances()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js b/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js index 695ef81bf71fb..c05366c294e87 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js +++ b/x-pack/test/functional/apps/monitoring/kibana/instances_mb.js @@ -26,6 +26,8 @@ export default function ({ getService, getPageObjects }) { } ); + await clusterOverview.closeAlertsModal(); + // go to kibana instances await clusterOverview.clickKibanaInstances(); expect(await instances.isOnInstances()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/kibana/overview.js b/x-pack/test/functional/apps/monitoring/kibana/overview.js index 060c368563a95..a366033e1baf6 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/overview.js +++ b/x-pack/test/functional/apps/monitoring/kibana/overview.js @@ -22,6 +22,8 @@ export default function ({ getService, getPageObjects }) { to: 'Aug 29, 2017 @ 17:25:44.142', }); + await clusterOverview.closeAlertsModal(); + // go to kibana overview await clusterOverview.clickKibanaOverview(); expect(await overview.isOnOverview()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/kibana/overview_mb.js b/x-pack/test/functional/apps/monitoring/kibana/overview_mb.js index 5f8b926e5d17c..519903c201474 100644 --- a/x-pack/test/functional/apps/monitoring/kibana/overview_mb.js +++ b/x-pack/test/functional/apps/monitoring/kibana/overview_mb.js @@ -25,6 +25,8 @@ export default function ({ getService, getPageObjects }) { } ); + await clusterOverview.closeAlertsModal(); + // go to kibana overview await clusterOverview.clickKibanaOverview(); expect(await overview.isOnOverview()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js index 2f698f83912c1..72a6ff8e1af23 100644 --- a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js +++ b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js @@ -24,6 +24,8 @@ export default function ({ getService, getPageObjects }) { to: 'Jan 22, 2018 @ 09:41:00.000', }); + await overview.closeAlertsModal(); + // go to pipelines listing await overview.clickLsPipelines(); expect(await pipelinesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/apps/monitoring/logstash/pipelines_mb.js b/x-pack/test/functional/apps/monitoring/logstash/pipelines_mb.js index 508f919d97efd..89afac834414b 100644 --- a/x-pack/test/functional/apps/monitoring/logstash/pipelines_mb.js +++ b/x-pack/test/functional/apps/monitoring/logstash/pipelines_mb.js @@ -24,6 +24,8 @@ export default function ({ getService, getPageObjects }) { to: 'Jan 22, 2018 @ 09:41:00.000', }); + await overview.closeAlertsModal(); + // go to pipelines listing await overview.clickLsPipelines(); expect(await pipelinesList.isOnListing()).to.be(true); diff --git a/x-pack/test/functional/page_objects/monitoring_page.ts b/x-pack/test/functional/page_objects/monitoring_page.ts index 151b6733509e6..acd9a443eb7ce 100644 --- a/x-pack/test/functional/page_objects/monitoring_page.ts +++ b/x-pack/test/functional/page_objects/monitoring_page.ts @@ -16,6 +16,10 @@ export class MonitoringPageObject extends FtrService { return this.testSubjects.getVisibleText('accessDeniedTitle'); } + async closeAlertsModal() { + return this.testSubjects.click('alerts-modal-button'); + } + async clickBreadcrumb(subj: string) { return this.testSubjects.click(subj); } diff --git a/x-pack/test/functional/services/monitoring/cluster_overview.js b/x-pack/test/functional/services/monitoring/cluster_overview.js index 3da0c82c87a63..5128cbffd34cf 100644 --- a/x-pack/test/functional/services/monitoring/cluster_overview.js +++ b/x-pack/test/functional/services/monitoring/cluster_overview.js @@ -75,6 +75,10 @@ export function MonitoringClusterOverviewProvider({ getService }) { return testSubjects.exists(SUBJ_CLUSTER_ALERTS); } + closeAlertsModal() { + return testSubjects.click('alerts-modal-button'); + } + getEsStatus() { return testSubjects.getVisibleText(SUBJ_ES_STATUS); }