From f60abf368a53a4ab285c53868d2d1dd7fb011418 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 16 Nov 2020 13:11:23 +0100 Subject: [PATCH 01/51] [Search] Send to background UI (#81793) Also adds xpack.data_enhanced.search.sendToBackground.enabled config option --- src/dev/storybook/aliases.ts | 1 + .../data/common/search/session/mocks.ts | 3 +- .../plugins/data_enhanced/.storybook/main.js | 7 + x-pack/plugins/data_enhanced/config.ts | 17 ++ x-pack/plugins/data_enhanced/kibana.json | 2 +- x-pack/plugins/data_enhanced/public/index.ts | 5 +- x-pack/plugins/data_enhanced/public/plugin.ts | 22 +- .../data_enhanced/public/search/index.ts | 7 + .../background_session_indicator.scss | 23 ++ .../background_session_indicator.stories.tsx | 30 ++ .../background_session_indicator.test.tsx | 98 ++++++ .../background_session_indicator.tsx | 286 ++++++++++++++++++ .../ui/background_session_indicator/index.tsx | 23 ++ .../background_session_view_state.ts | 33 ++ ...cted_background_session_indicator.test.tsx | 37 +++ ...connected_background_session_indicator.tsx | 32 ++ .../index.ts | 11 + .../data_enhanced/public/search/ui/index.ts | 7 + x-pack/plugins/data_enhanced/server/index.ts | 12 +- 19 files changed, 649 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/.storybook/main.js create mode 100644 x-pack/plugins/data_enhanced/config.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/index.ts diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 153725fc48e7b..36c742dc40403 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -22,6 +22,7 @@ export const storybookAliases = { canvas: 'x-pack/plugins/canvas/storybook', codeeditor: 'src/plugins/kibana_react/public/code_editor/.storybook', dashboard_enhanced: 'x-pack/plugins/dashboard_enhanced/.storybook', + data_enhanced: 'x-pack/plugins/data_enhanced/.storybook', embeddable: 'src/plugins/embeddable/.storybook', infra: 'x-pack/plugins/infra/.storybook', security_solution: 'x-pack/plugins/security_solution/.storybook', diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/common/search/session/mocks.ts index 2b64bbbd27565..370faaa640c56 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/common/search/session/mocks.ts @@ -17,6 +17,7 @@ * under the License. */ +import { BehaviorSubject } from 'rxjs'; import { ISessionService } from './types'; export function getSessionServiceMock(): jest.Mocked { @@ -25,6 +26,6 @@ export function getSessionServiceMock(): jest.Mocked { start: jest.fn(), restore: jest.fn(), getSessionId: jest.fn(), - getSession$: jest.fn(), + getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), }; } diff --git a/x-pack/plugins/data_enhanced/.storybook/main.js b/x-pack/plugins/data_enhanced/.storybook/main.js new file mode 100644 index 0000000000000..1818aa44a9399 --- /dev/null +++ b/x-pack/plugins/data_enhanced/.storybook/main.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts new file mode 100644 index 0000000000000..9838f0959ef19 --- /dev/null +++ b/x-pack/plugins/data_enhanced/config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + search: schema.object({ + sendToBackground: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), + }), +}); + +export type ConfigSchema = TypeOf; diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json index 5ded0f8f0dec3..bc7c8410d3df1 100644 --- a/x-pack/plugins/data_enhanced/kibana.json +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -12,5 +12,5 @@ "optionalPlugins": ["kibanaUtils", "usageCollection"], "server": true, "ui": true, - "requiredBundles": ["kibanaUtils"] + "requiredBundles": ["kibanaUtils", "kibanaReact"] } diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index 22ac0c9883966..7fe34e21fde5c 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializerContext } from 'kibana/public'; import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plugin'; +import { ConfigSchema } from '../config'; -export const plugin = () => new DataEnhancedPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new DataEnhancedPlugin(initializerContext); export { DataEnhancedSetup, DataEnhancedStart }; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 43ad4a9ed9b8b..948858a5ed4c1 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import React from 'react'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; + import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; - import { EnhancedSearchInterceptor } from './search/search_interceptor'; +import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; +import { createConnectedBackgroundSessionIndicator } from './search'; +import { ConfigSchema } from '../config'; export interface DataEnhancedSetupDependencies { data: DataPublicPluginSetup; @@ -25,6 +29,8 @@ export class DataEnhancedPlugin implements Plugin { private enhancedSearchInterceptor!: EnhancedSearchInterceptor; + constructor(private initializerContext: PluginInitializerContext) {} + public setup( core: CoreSetup, { data }: DataEnhancedSetupDependencies @@ -52,6 +58,18 @@ export class DataEnhancedPlugin public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { setAutocompleteService(plugins.data.autocomplete); + + if (this.initializerContext.config.get().search.sendToBackground.enabled) { + core.chrome.setBreadcrumbsAppendExtension({ + content: toMountPoint( + React.createElement( + createConnectedBackgroundSessionIndicator({ + sessionService: plugins.data.search.session, + }) + ) + ), + }); + } } public stop() { diff --git a/x-pack/plugins/data_enhanced/public/search/index.ts b/x-pack/plugins/data_enhanced/public/search/index.ts new file mode 100644 index 0000000000000..1a33812ca8566 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './ui'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss new file mode 100644 index 0000000000000..2d13d320ae78b --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss @@ -0,0 +1,23 @@ +.backgroundSessionIndicator { + padding: 0 $euiSizeXS; +} + +@include euiBreakpoint('xs', 's') { + .backgroundSessionIndicator__popoverContainer.euiFlexGroup--responsive .euiFlexItem { + margin-bottom: $euiSizeXS !important; + } +} + +.backgroundSessionIndicator__verticalDivider { + @include euiBreakpoint('xs', 's') { + margin-left: $euiSizeXS; + padding-left: $euiSizeXS; + } + + @include euiBreakpoint('m', 'l', 'xl') { + border-left: $euiBorderThin; + align-self: stretch; + margin-left: $euiSizeS; + padding-left: $euiSizeS; + } +} diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx new file mode 100644 index 0000000000000..9cef76c62279c --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { BackgroundSessionIndicator } from './background_session_indicator'; +import { BackgroundSessionViewState } from '../connected_background_session_indicator'; + +storiesOf('components/BackgroundSessionIndicator', module).add('default', () => ( + <> +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +)); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx new file mode 100644 index 0000000000000..5b7ab2e4f9b1f --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ReactNode } from 'react'; +import { screen, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { BackgroundSessionIndicator } from './background_session_indicator'; +import { BackgroundSessionViewState } from '../connected_background_session_indicator'; +import { IntlProvider } from 'react-intl'; + +function Container({ children }: { children?: ReactNode }) { + return {children}; +} + +test('Loading state', async () => { + const onCancel = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Loading results')); + await userEvent.click(screen.getByText('Cancel')); + + expect(onCancel).toBeCalled(); +}); + +test('Completed state', async () => { + const onSave = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Results loaded')); + await userEvent.click(screen.getByText('Save')); + + expect(onSave).toBeCalled(); +}); + +test('Loading in the background state', async () => { + const onCancel = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Loading results in the background')); + await userEvent.click(screen.getByText('Cancel')); + + expect(onCancel).toBeCalled(); +}); + +test('BackgroundCompleted state', async () => { + const onViewSession = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Results loaded in the background')); + await userEvent.click(screen.getByText('View background sessions')); + + expect(onViewSession).toBeCalled(); +}); + +test('Restored state', async () => { + const onRefresh = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Results no longer current')); + await userEvent.click(screen.getByText('Refresh')); + + expect(onRefresh).toBeCalled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx new file mode 100644 index 0000000000000..b55bd6b655371 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiButtonEmpty, + EuiButtonEmptyProps, + EuiButtonIcon, + EuiButtonIconProps, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPopover, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { BackgroundSessionViewState } from '../connected_background_session_indicator'; +import './background_session_indicator.scss'; + +export interface BackgroundSessionIndicatorProps { + state: BackgroundSessionViewState; + onContinueInBackground?: () => void; + onCancel?: () => void; + onViewBackgroundSessions?: () => void; + onSaveResults?: () => void; + onRefresh?: () => void; +} + +type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; + +const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonProps) => ( + + + +); + +const ContinueInBackgroundButton = ({ + onContinueInBackground = () => {}, + buttonProps = {}, +}: ActionButtonProps) => ( + + + +); + +const ViewBackgroundSessionsButton = ({ + onViewBackgroundSessions = () => {}, + buttonProps = {}, +}: ActionButtonProps) => ( + // TODO: make this a link + + + +); + +const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonProps) => ( + + + +); + +const SaveButton = ({ onSaveResults = () => {}, buttonProps = {} }: ActionButtonProps) => ( + + + +); + +const backgroundSessionIndicatorViewStateToProps: { + [state in BackgroundSessionViewState]: { + button: Pick & { tooltipText: string }; + popover: { + text: string; + primaryAction?: React.ComponentType; + secondaryAction?: React.ComponentType; + }; + }; +} = { + [BackgroundSessionViewState.Loading]: { + button: { + color: 'subdued', + iconType: 'clock', + 'aria-label': i18n.translate( + 'xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel', + { defaultMessage: 'Loading results' } + ), + tooltipText: i18n.translate( + 'xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText', + { defaultMessage: 'Loading results' } + ), + }, + popover: { + text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingResultsText', { + defaultMessage: 'Loading', + }), + primaryAction: CancelButton, + secondaryAction: ContinueInBackgroundButton, + }, + }, + [BackgroundSessionViewState.Completed]: { + button: { + color: 'subdued', + iconType: 'checkInCircleFilled', + 'aria-label': i18n.translate( + 'xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel', + { + defaultMessage: 'Results loaded', + } + ), + tooltipText: i18n.translate( + 'xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText', + { + defaultMessage: 'Results loaded', + } + ), + }, + popover: { + text: i18n.translate('xpack.data.backgroundSessionIndicator.resultsLoadedText', { + defaultMessage: 'Results loaded', + }), + primaryAction: SaveButton, + secondaryAction: ViewBackgroundSessionsButton, + }, + }, + [BackgroundSessionViewState.BackgroundLoading]: { + button: { + iconType: EuiLoadingSpinner, + 'aria-label': i18n.translate( + 'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel', + { + defaultMessage: 'Loading results in the background', + } + ), + tooltipText: i18n.translate( + 'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText', + { + defaultMessage: 'Loading results in the background', + } + ), + }, + popover: { + text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText', { + defaultMessage: 'Loading in the background', + }), + primaryAction: CancelButton, + secondaryAction: ViewBackgroundSessionsButton, + }, + }, + [BackgroundSessionViewState.BackgroundCompleted]: { + button: { + color: 'success', + iconType: 'checkInCircleFilled', + 'aria-label': i18n.translate( + 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText', + { + defaultMessage: 'Results loaded in the background', + } + ), + tooltipText: i18n.translate( + 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText', + { + defaultMessage: 'Results loaded in the background', + } + ), + }, + popover: { + text: i18n.translate( + 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText', + { + defaultMessage: 'Results loaded', + } + ), + primaryAction: ViewBackgroundSessionsButton, + }, + }, + [BackgroundSessionViewState.Restored]: { + button: { + color: 'warning', + iconType: 'refresh', + 'aria-label': i18n.translate( + 'xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel', + { + defaultMessage: 'Results no longer current', + } + ), + tooltipText: i18n.translate( + 'xpack.data.backgroundSessionIndicator.restoredResultsTooltipText', + { + defaultMessage: 'Results no longer current', + } + ), + }, + popover: { + text: i18n.translate('xpack.data.backgroundSessionIndicator.restoredText', { + defaultMessage: 'Results no longer current', + }), + primaryAction: RefreshButton, + secondaryAction: ViewBackgroundSessionsButton, + }, + }, +}; + +const VerticalDivider: React.FC = () => ( +
+); + +export const BackgroundSessionIndicator: React.FC = (props) => { + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen); + const closePopover = () => setIsPopoverOpen(false); + + const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state]; + + return ( + + + + } + > + + + +

{popover.text}

+
+
+ + + {popover.primaryAction && ( + + + + )} + {popover.primaryAction && popover.secondaryAction && } + {popover.secondaryAction && ( + + + + )} + + +
+
+ ); +}; + +// React.lazy() needs default: +// eslint-disable-next-line import/no-default-export +export default BackgroundSessionIndicator; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx new file mode 100644 index 0000000000000..55c8c453dd5d2 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiDelayRender, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import type { BackgroundSessionIndicatorProps } from './background_session_indicator'; +export type { BackgroundSessionIndicatorProps }; + +const Fallback = () => ( + + + +); + +const LazyBackgroundSessionIndicator = React.lazy(() => import('./background_session_indicator')); +export const BackgroundSessionIndicator = (props: BackgroundSessionIndicatorProps) => ( + }> + + +); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts new file mode 100644 index 0000000000000..b75c2a536f624 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum BackgroundSessionViewState { + /** + * Pending search request has not been sent to the background yet + */ + Loading = 'loading', + + /** + * No action was taken and the page completed loading without background session creation. + */ + Completed = 'completed', + + /** + * Search request was sent to the background. + * The page is loading in background. + */ + BackgroundLoading = 'backgroundLoading', + + /** + * Page load completed with background session created. + */ + BackgroundCompleted = 'backgroundCompleted', + + /** + * Revisiting the page after background completion + */ + Restored = 'restored', +} diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx new file mode 100644 index 0000000000000..b21081e10bbe1 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, waitFor } from '@testing-library/react'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; +import { BehaviorSubject } from 'rxjs'; +import { ISessionService } from '../../../../../../../src/plugins/data/public'; + +const sessionService = dataPluginMock.createStartContract().search.session as jest.Mocked< + ISessionService +>; + +test("shouldn't show indicator in case no active search session", async () => { + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService }); + const { getByTestId, container } = render(); + + // make sure `backgroundSessionIndicator` isn't appearing after some time (lazy-loading) + await expect( + waitFor(() => getByTestId('backgroundSessionIndicator'), { timeout: 100 }) + ).rejects.toThrow(); + expect(container).toMatchInlineSnapshot(`
`); +}); + +test('should show indicator in case there is an active search session', async () => { + const session$ = new BehaviorSubject('session_id'); + sessionService.getSession$.mockImplementation(() => session$); + sessionService.getSessionId.mockImplementation(() => session$.getValue()); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService }); + const { getByTestId } = render(); + + await waitFor(() => getByTestId('backgroundSessionIndicator')); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx new file mode 100644 index 0000000000000..d097a1aecb66a --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { BackgroundSessionIndicator } from '../background_session_indicator'; +import { ISessionService } from '../../../../../../../src/plugins/data/public/'; +import { BackgroundSessionViewState } from './background_session_view_state'; + +export interface BackgroundSessionIndicatorDeps { + sessionService: ISessionService; +} + +export const createConnectedBackgroundSessionIndicator = ({ + sessionService, +}: BackgroundSessionIndicatorDeps): React.FC => { + const sessionId$ = sessionService.getSession$(); + const hasActiveSession$ = sessionId$.pipe( + map((sessionId) => !!sessionId), + distinctUntilChanged() + ); + + return () => { + const isSession = useObservable(hasActiveSession$, !!sessionService.getSessionId()); + if (!isSession) return null; + return ; + }; +}; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts new file mode 100644 index 0000000000000..adbb6edbbfcf3 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + BackgroundSessionIndicatorDeps, + createConnectedBackgroundSessionIndicator, +} from './connected_background_session_indicator'; +export { BackgroundSessionViewState } from './background_session_view_state'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/index.ts new file mode 100644 index 0000000000000..04201325eb5db --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './connected_background_session_indicator'; diff --git a/x-pack/plugins/data_enhanced/server/index.ts b/x-pack/plugins/data_enhanced/server/index.ts index a0edd2e26ebef..c3907b3b67439 100644 --- a/x-pack/plugins/data_enhanced/server/index.ts +++ b/x-pack/plugins/data_enhanced/server/index.ts @@ -4,10 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'kibana/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; import { EnhancedDataServerPlugin } from './plugin'; +import { configSchema, ConfigSchema } from '../config'; -export function plugin(initializerContext: PluginInitializerContext) { +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + search: true, + }, + schema: configSchema, +}; + +export function plugin(initializerContext: PluginInitializerContext) { return new EnhancedDataServerPlugin(initializerContext); } From 9d40dab794da7f00ab5179dd52c404b7e659233b Mon Sep 17 00:00:00 2001 From: kaisecheng <69120390+kaisecheng@users.noreply.github.com> Date: Mon, 16 Nov 2020 14:00:18 +0100 Subject: [PATCH 02/51] fix logstash central pipeline management test (#83281) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/test/functional/apps/logstash/pipeline_create.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/apps/logstash/pipeline_create.js b/x-pack/test/functional/apps/logstash/pipeline_create.js index c3cf102c908a0..2b70dc1832367 100644 --- a/x-pack/test/functional/apps/logstash/pipeline_create.js +++ b/x-pack/test/functional/apps/logstash/pipeline_create.js @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { omit } from 'lodash'; export default function ({ getService, getPageObjects }) { const browser = getService('browser'); @@ -15,8 +16,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['logstash']); const retry = getService('retry'); - // FLAKY: https://github.com/elastic/kibana/issues/83231 - describe.skip('pipeline create new', () => { + describe('pipeline create new', () => { let originalWindowSize; before(async () => { @@ -89,6 +89,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.logstash.gotoPipelineList(); await pipelineList.assertExists(); const originalRows = await pipelineList.readRows(); + const originalRowsWithoutTime = originalRows.map((row) => omit(row, 'lastModified')); await PageObjects.logstash.gotoNewPipelineEditor(); await pipelineEditor.clickCancel(); @@ -96,7 +97,8 @@ export default function ({ getService, getPageObjects }) { await retry.try(async () => { await pipelineList.assertExists(); const currentRows = await pipelineList.readRows(); - expect(originalRows).to.eql(currentRows); + const currentRowsWithoutTime = currentRows.map((row) => omit(row, 'lastModified')); + expect(originalRowsWithoutTime).to.eql(currentRowsWithoutTime); }); }); }); From 01b1710eb7c55d9c7a421f7d0e1b53d05a203ce0 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 16 Nov 2020 15:34:40 +0200 Subject: [PATCH 03/51] [Security Solution][Case] Change case connector minimum required license to basic (#83401) --- x-pack/plugins/case/server/connectors/case/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index f2f8f659f3a2c..dc647d288ec65 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -33,7 +33,7 @@ export function getActionType({ }: GetActionTypeParams): CaseActionType { return { id: CASE_ACTION_TYPE_ID, - minimumLicenseRequired: 'gold', + minimumLicenseRequired: 'basic', name: i18n.NAME, validate: { config: CaseConfigurationSchema, From c8b8a0ae9c27c43163a049994bccd0880f6d080f Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 16 Nov 2020 14:35:12 +0100 Subject: [PATCH 04/51] [Lens] Avoid unnecessary data fetching on dimension flyout open (#82957) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../operations/definitions/helpers.tsx | 30 +++++ .../definitions/ranges/advanced_editor.tsx | 9 +- .../definitions/ranges/range_editor.tsx | 16 +-- .../definitions/ranges/ranges.test.tsx | 111 +++++++++++------- .../terms/values_range_input.test.tsx | 20 ++-- .../definitions/terms/values_range_input.tsx | 6 +- 6 files changed, 129 insertions(+), 63 deletions(-) create mode 100644 x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx new file mode 100644 index 0000000000000..a5c08a93467af --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useRef } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; + +export const useDebounceWithOptions = ( + fn: Function, + { skipFirstRender }: { skipFirstRender: boolean } = { skipFirstRender: false }, + ms?: number | undefined, + deps?: React.DependencyList | undefined +) => { + const isFirstRender = useRef(true); + const newDeps = [...(deps || []), isFirstRender]; + + return useDebounce( + () => { + if (skipFirstRender && isFirstRender.current) { + isFirstRender.current = false; + return; + } + return fn(); + }, + ms, + newDeps + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index 2eb971aa03c55..95c7e3533ee09 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -8,7 +8,6 @@ import './advanced_editor.scss'; import React, { useState, MouseEventHandler } from 'react'; import { i18n } from '@kbn/i18n'; -import useDebounce from 'react-use/lib/useDebounce'; import { EuiFlexGroup, EuiFlexItem, @@ -31,6 +30,7 @@ import { DraggableBucketContainer, LabelInput, } from '../shared_components'; +import { useDebounceWithOptions } from '../helpers'; const generateId = htmlIdGenerator(); @@ -208,12 +208,13 @@ export const AdvancedRangeEditor = ({ const lastIndex = localRanges.length - 1; - // Update locally all the time, but bounce the parents prop function - // to aviod too many requests - useDebounce( + // Update locally all the time, but bounce the parents prop function to aviod too many requests + // Avoid to trigger on first render + useDebounceWithOptions( () => { setRanges(localRanges.map(({ id, ...rest }) => ({ ...rest }))); }, + { skipFirstRender: true }, TYPING_DEBOUNCE_TIME, [localRanges] ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx index a18c47f9dedd1..df955be6b490a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/range_editor.tsx @@ -6,7 +6,6 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import useDebounce from 'react-use/lib/useDebounce'; import { EuiButtonEmpty, EuiFormRow, @@ -21,6 +20,7 @@ import { IFieldFormat } from 'src/plugins/data/public'; import { RangeColumnParams, UpdateParamsFnType, MODES_TYPES } from './ranges'; import { AdvancedRangeEditor } from './advanced_editor'; import { TYPING_DEBOUNCE_TIME, MODES, MIN_HISTOGRAM_BARS } from './constants'; +import { useDebounceWithOptions } from '../helpers'; const BaseRangeEditor = ({ maxBars, @@ -37,10 +37,11 @@ const BaseRangeEditor = ({ }) => { const [maxBarsValue, setMaxBarsValue] = useState(String(maxBars)); - useDebounce( + useDebounceWithOptions( () => { onMaxBarsChange(Number(maxBarsValue)); }, + { skipFirstRender: true }, TYPING_DEBOUNCE_TIME, [maxBarsValue] ); @@ -151,13 +152,14 @@ export const RangeEditor = ({ }) => { const [isAdvancedEditor, toggleAdvancedEditor] = useState(params.type === MODES.Range); - // if the maxBars in the params is set to auto refresh it with the default value - // only on bootstrap + // if the maxBars in the params is set to auto refresh it with the default value only on bootstrap useEffect(() => { - if (params.maxBars !== maxBars) { - setParam('maxBars', maxBars); + if (!isAdvancedEditor) { + if (params.maxBars !== maxBars) { + setParam('maxBars', maxBars); + } } - }, [maxBars, params.maxBars, setParam]); + }, [maxBars, params.maxBars, setParam, isAdvancedEditor]); if (isAdvancedEditor) { return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index ce015284e544b..87dcdb45cf58f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -284,7 +284,11 @@ describe('ranges', () => { /> ); + // There's a useEffect in the component that updates the value on bootstrap + // because there's a debouncer, wait a bit before calling onChange act(() => { + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + instance.find(EuiRange).prop('onChange')!( { currentTarget: { @@ -293,26 +297,27 @@ describe('ranges', () => { } as React.ChangeEvent, true ); + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + }); - expect(setStateSpy).toHaveBeenCalledWith({ - ...state, - layers: { - first: { - ...state.layers.first, - columns: { - ...state.layers.first.columns, - col1: { - ...state.layers.first.columns.col1, - params: { - ...state.layers.first.columns.col1.params, - maxBars: MAX_HISTOGRAM_VALUE, - }, + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: MAX_HISTOGRAM_VALUE, }, }, }, }, - }); + }, }); }); @@ -330,59 +335,65 @@ describe('ranges', () => { /> ); + // There's a useEffect in the component that updates the value on bootstrap + // because there's a debouncer, wait a bit before calling onChange act(() => { + jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); // minus button instance .find('[data-test-subj="lns-indexPattern-range-maxBars-minus"]') .find('button') .prop('onClick')!({} as ReactMouseEvent); jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + }); - expect(setStateSpy).toHaveBeenCalledWith({ - ...state, - layers: { - first: { - ...state.layers.first, - columns: { - ...state.layers.first.columns, - col1: { - ...state.layers.first.columns.col1, - params: { - ...state.layers.first.columns.col1.params, - maxBars: GRANULARITY_DEFAULT_VALUE - GRANULARITY_STEP, - }, + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE - GRANULARITY_STEP, }, }, }, }, - }); + }, + }); + act(() => { // plus button instance .find('[data-test-subj="lns-indexPattern-range-maxBars-plus"]') .find('button') .prop('onClick')!({} as ReactMouseEvent); jest.advanceTimersByTime(TYPING_DEBOUNCE_TIME * 4); + }); - expect(setStateSpy).toHaveBeenCalledWith({ - ...state, - layers: { - first: { - ...state.layers.first, - columns: { - ...state.layers.first.columns, - col1: { - ...state.layers.first.columns.col1, - params: { - ...state.layers.first.columns.col1.params, - maxBars: GRANULARITY_DEFAULT_VALUE, - }, + expect(setStateSpy).toHaveBeenCalledWith({ + ...state, + layers: { + first: { + ...state.layers.first, + columns: { + ...state.layers.first.columns, + col1: { + ...state.layers.first.columns.col1, + params: { + ...state.layers.first.columns.col1.params, + maxBars: GRANULARITY_DEFAULT_VALUE, }, }, }, }, - }); + }, }); + // }); }); }); @@ -749,6 +760,22 @@ describe('ranges', () => { ); }); + it('should not update the state on mount', () => { + const setStateSpy = jest.fn(); + + mount( + + ); + expect(setStateSpy.mock.calls.length).toBe(0); + }); + it('should not reset formatters when switching between custom ranges and auto histogram', () => { const setStateSpy = jest.fn(); // now set a format on the range operation diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx index 18b9b5b1e8b98..759bda43efe67 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.test.tsx @@ -20,6 +20,13 @@ describe('ValuesRangeInput', () => { expect(instance.find(EuiRange).prop('value')).toEqual('5'); }); + it('should not run onChange function on mount', () => { + const onChangeSpy = jest.fn(); + shallow(); + + expect(onChangeSpy.mock.calls.length).toBe(0); + }); + it('should run onChange function on update', () => { const onChangeSpy = jest.fn(); const instance = shallow(); @@ -30,11 +37,10 @@ describe('ValuesRangeInput', () => { ); }); expect(instance.find(EuiRange).prop('value')).toEqual('7'); - // useDebounce runs on initialization and on change - expect(onChangeSpy.mock.calls.length).toBe(2); - expect(onChangeSpy.mock.calls[0][0]).toBe(5); - expect(onChangeSpy.mock.calls[1][0]).toBe(7); + expect(onChangeSpy.mock.calls.length).toBe(1); + expect(onChangeSpy.mock.calls[0][0]).toBe(7); }); + it('should not run onChange function on update when value is out of 1-100 range', () => { const onChangeSpy = jest.fn(); const instance = shallow(); @@ -46,9 +52,7 @@ describe('ValuesRangeInput', () => { }); instance.update(); expect(instance.find(EuiRange).prop('value')).toEqual('107'); - // useDebounce only runs on initialization - expect(onChangeSpy.mock.calls.length).toBe(2); - expect(onChangeSpy.mock.calls[0][0]).toBe(5); - expect(onChangeSpy.mock.calls[1][0]).toBe(100); + expect(onChangeSpy.mock.calls.length).toBe(1); + expect(onChangeSpy.mock.calls[0][0]).toBe(100); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx index ef42f2d4a7175..7018ba3083f04 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_range_input.tsx @@ -5,9 +5,9 @@ */ import React, { useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; import { EuiRange } from '@elastic/eui'; +import { useDebounceWithOptions } from '../helpers'; export const ValuesRangeInput = ({ value, @@ -20,7 +20,8 @@ export const ValuesRangeInput = ({ const MAX_NUMBER_OF_VALUES = 100; const [inputValue, setInputValue] = useState(String(value)); - useDebounce( + + useDebounceWithOptions( () => { if (inputValue === '') { return; @@ -28,6 +29,7 @@ export const ValuesRangeInput = ({ const inputNumber = Number(inputValue); onChange(Math.min(MAX_NUMBER_OF_VALUES, Math.max(inputNumber, MIN_NUMBER_OF_VALUES))); }, + { skipFirstRender: true }, 256, [inputValue] ); From 3ba7758a4fad65d0df62eafb4ffd03084195eed0 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Mon, 16 Nov 2020 08:26:41 -0600 Subject: [PATCH 05/51] fix tall vislib charts in visualize (#83340) --- .../public/vislib/_vislib_vis_type.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss b/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss index 843bb9d3f03eb..9e737fc87e895 100644 --- a/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss +++ b/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss @@ -29,3 +29,15 @@ min-height: 0; min-width: 0; } + +.vislib__wrapper { + position: relative; +} + +.vislib__container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} From 3f51bf5e9f3be53cba23a4bc55d658ec0c227ddd Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 16 Nov 2020 09:23:28 -0600 Subject: [PATCH 06/51] Fix advanced settings category sorting (#83394) In the advanced settings categories, "Observability" and "Machine Learning" were using uppercase letters in their keys while everything else was using lowercase This caused them both to show up before the rest of the options in the dropdown and in the sorting in the advanced settings UI. Add keys for them to the get_category_name module in the advanced settings plugin and use those keys in the plugins that apply these categories. This also makes it so i18n keys are available for these items. Fixes #81974. --- .../public/management_app/lib/get_category_name.ts | 6 ++++++ x-pack/plugins/apm/server/ui_settings.ts | 4 ++-- x-pack/plugins/ml/server/lib/register_settings.ts | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts index 31df6875c97d9..c8fbe8009c9bb 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/get_category_name.ts @@ -25,6 +25,12 @@ const names: Record = { general: i18n.translate('advancedSettings.categoryNames.generalLabel', { defaultMessage: 'General', }), + machineLearning: i18n.translate('advancedSettings.categoryNames.machineLearningLabel', { + defaultMessage: 'Machine Learning', + }), + observability: i18n.translate('advancedSettings.categoryNames.observabilityLabel', { + defaultMessage: 'Observability', + }), timelion: i18n.translate('advancedSettings.categoryNames.timelionLabel', { defaultMessage: 'Timelion', }), diff --git a/x-pack/plugins/apm/server/ui_settings.ts b/x-pack/plugins/apm/server/ui_settings.ts index fe5b11d89d716..4932d9f79a383 100644 --- a/x-pack/plugins/apm/server/ui_settings.ts +++ b/x-pack/plugins/apm/server/ui_settings.ts @@ -17,7 +17,7 @@ import { */ export const uiSettings: Record> = { [enableCorrelations]: { - category: ['Observability'], + category: ['observability'], name: i18n.translate('xpack.apm.enableCorrelationsExperimentName', { defaultMessage: 'APM Correlations', }), @@ -32,7 +32,7 @@ export const uiSettings: Record> = { schema: schema.boolean(), }, [enableServiceOverview]: { - category: ['Observability'], + category: ['observability'], name: i18n.translate('xpack.apm.enableServiceOverviewExperimentName', { defaultMessage: 'APM Service overview', }), diff --git a/x-pack/plugins/ml/server/lib/register_settings.ts b/x-pack/plugins/ml/server/lib/register_settings.ts index a9ee24fbb5cea..0cdaaadf7f172 100644 --- a/x-pack/plugins/ml/server/lib/register_settings.ts +++ b/x-pack/plugins/ml/server/lib/register_settings.ts @@ -27,7 +27,7 @@ export function registerKibanaSettings(coreSetup: CoreSetup) { defaultMessage: 'Sets the file size limit when importing data in the File Data Visualizer. The highest supported value for this setting is 1GB.', }), - category: ['Machine Learning'], + category: ['machineLearning'], schema: schema.string(), validation: { regexString: '\\d+[mMgG][bB]', @@ -49,7 +49,7 @@ export function registerKibanaSettings(coreSetup: CoreSetup) { 'Use the default time filter in the Single Metric Viewer and Anomaly Explorer. If not enabled, the results for the full time range of the job are displayed.', } ), - category: ['Machine Learning'], + category: ['machineLearning'], }, [ANOMALY_DETECTION_DEFAULT_TIME_RANGE]: { name: i18n.translate('xpack.ml.advancedSettings.anomalyDetectionDefaultTimeRangeName', { @@ -69,7 +69,7 @@ export function registerKibanaSettings(coreSetup: CoreSetup) { to: schema.string(), }), requiresPageReload: true, - category: ['Machine Learning'], + category: ['machineLearning'], }, }); } From b8b880662868e26c1264b6f68dcc968285436a08 Mon Sep 17 00:00:00 2001 From: Dan Panzarella Date: Mon, 16 Nov 2020 11:19:03 -0500 Subject: [PATCH 07/51] [Security Solution] Gracefully handle errors in detection rules install (#83306) --- .../routes/rules/add_prepackaged_rules_route.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index f885445c29b04..a3b378a6ef04a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -115,12 +115,15 @@ export const createPrepackagedRules = async ( ); } } - const result = await Promise.all([ - installPrepackagedRules(alertsClient, rulesToInstall, signalsIndex), - installPrepackagedTimelines(maxTimelineImportExportSize, frameworkRequest, true), - ]); + + await Promise.all(installPrepackagedRules(alertsClient, rulesToInstall, signalsIndex)); + const timeline = await installPrepackagedTimelines( + maxTimelineImportExportSize, + frameworkRequest, + true + ); const [prepackagedTimelinesResult, timelinesErrors] = validate( - result[1], + timeline, importTimelineResultSchema ); await updatePrepackagedRules(alertsClient, savedObjectsClient, rulesToUpdate, signalsIndex); From e1500bf86af5d4e1db0b2a8c92ea26504e138b98 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 16 Nov 2020 17:22:35 +0100 Subject: [PATCH 08/51] [Lens] Make the dimension flyout panel stay close on outside click (#83059) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../definitions/filters/filter_popover.tsx | 20 +++++++++++------- .../definitions/ranges/advanced_editor.tsx | 21 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index b023a9a5a3ec5..b9d9d6306b9ae 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -32,9 +32,13 @@ export const FilterPopover = ({ const [isPopoverOpen, setIsPopoverOpen] = useState(false); const inputRef = React.useRef(); - const setPopoverOpen = (isOpen: boolean) => { - setIsPopoverOpen(isOpen); - setIsOpenByCreation(isOpen); + const closePopover = () => { + if (isOpenByCreation) { + setIsOpenByCreation(false); + } + if (isPopoverOpen) { + setIsPopoverOpen(false); + } }; const setFilterLabel = (label: string) => setFilter({ ...filter, label }); @@ -57,14 +61,14 @@ export const FilterPopover = ({ panelClassName="lnsIndexPatternDimensionEditor__filtersEditor" isOpen={isOpenByCreation || isPopoverOpen} ownFocus - closePopover={() => { - setPopoverOpen(false); - }} + closePopover={() => closePopover()} button={