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/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/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/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts
index 43ad4a9ed9b8b..93564373f6fce 100644
--- a/x-pack/plugins/data_enhanced/public/plugin.ts
+++ b/x-pack/plugins/data_enhanced/public/plugin.ts
@@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React from 'react';
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { setAutocompleteService } from './services';
-import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete';
+import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete';
import { EnhancedSearchInterceptor } from './search/search_interceptor';
+import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
+import { createConnectedBackgroundSessionIndicator } from './ui/connected_background_session_indicator';
export interface DataEnhancedSetupDependencies {
data: DataPublicPluginSetup;
@@ -52,6 +55,14 @@ export class DataEnhancedPlugin
public start(core: CoreStart, plugins: DataEnhancedStartDependencies) {
setAutocompleteService(plugins.data.autocomplete);
+
+ core.chrome.setBreadcrumbsAppendExtension({
+ content: toMountPoint(
+ React.createElement(
+ createConnectedBackgroundSessionIndicator({ sessionService: plugins.data.search.session })
+ )
+ ),
+ });
}
public stop() {
diff --git a/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/background_session_indicator.stories.tsx
new file mode 100644
index 0000000000000..c94df39793547
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/public/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 '../background_session_state';
+
+storiesOf('components/BackgroundSessionIndicator', module).add('default', () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+));
diff --git a/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/background_session_indicator.tsx
new file mode 100644
index 0000000000000..66940ded77096
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/background_session_indicator.tsx
@@ -0,0 +1,188 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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,
+ EuiButtonIcon,
+ EuiButtonIconProps,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingSpinner,
+ EuiPopover,
+ EuiText,
+ EuiToolTip,
+} from '@elastic/eui';
+import { BackgroundSessionViewState } from '../background_session_state';
+
+export interface BackgroundSessionIndicatorProps {
+ state: BackgroundSessionViewState;
+ onContinueInBackground?: () => {};
+ onStopLoading?: () => {};
+ onViewBackgroundRequests?: () => {};
+ onSaveResults?: () => {};
+ onReload?: () => {};
+}
+
+const backgroundSessionIndicatorViewStateToProps: {
+ [state in BackgroundSessionViewState]: {
+ button: Pick & { tooltipText: string };
+ popover: {
+ text: string;
+ buttons: Array>;
+ };
+ };
+} = {
+ [BackgroundSessionViewState.Loading]: {
+ button: {
+ color: 'subdued',
+ iconType: 'clock',
+ 'aria-label': 'Loading results...',
+ tooltipText: 'Loading results',
+ },
+ popover: {
+ text: 'Loading',
+ buttons: [
+ ({ onStopLoading = () => {} }) => (
+
+ Cancel
+
+ ),
+ ({ onContinueInBackground = () => {} }) => (
+
+ Continue in background
+
+ ),
+ ],
+ },
+ },
+ [BackgroundSessionViewState.Completed]: {
+ button: {
+ color: 'subdued',
+ iconType: 'checkInCircleFilled',
+ 'aria-label': 'Results loaded',
+ tooltipText: 'Results loaded',
+ },
+ popover: {
+ text: 'Results loaded',
+ buttons: [
+ ({ onSaveResults = () => {} }) => (
+
+ Save results
+
+ ),
+ ({ onViewBackgroundRequests = () => {} }) => (
+ // TODO: make this a link
+
+ View requests
+
+ ),
+ ],
+ },
+ },
+ [BackgroundSessionViewState.BackgroundLoading]: {
+ button: {
+ iconType: EuiLoadingSpinner,
+ 'aria-label': 'Loading results in the background',
+ tooltipText: 'Loading results in the background',
+ },
+ popover: {
+ text: 'Loading in the background',
+ buttons: [
+ ({ onStopLoading = () => {} }) => (
+
+ Cancel
+
+ ),
+ ({ onViewBackgroundRequests = () => {} }) => (
+
+ View requests
+
+ ),
+ ],
+ },
+ },
+ [BackgroundSessionViewState.BackgroundCompleted]: {
+ button: {
+ color: 'success',
+ iconType: 'checkInCircleFilled',
+ 'aria-label': 'Results loaded in the background',
+ tooltipText: 'Results loaded in the background',
+ },
+ popover: {
+ text: 'Loaded in the background',
+ buttons: [
+ ({ onViewBackgroundRequests = () => {} }) => (
+
+ View background requests
+
+ ),
+ ],
+ },
+ },
+ [BackgroundSessionViewState.Restored]: {
+ button: {
+ color: 'warning',
+ iconType: 'refresh',
+ 'aria-label': 'Restored older results. The data is not current.',
+ tooltipText: 'Restored older results. The data is not current.',
+ },
+ popover: {
+ text: 'The data is not current',
+ buttons: [
+ ({ onReload = () => {} }) => (
+
+ Reload
+
+ ),
+ ],
+ },
+ },
+};
+
+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.buttons.map((Button, index) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+// React.lazy() needs default:
+// eslint-disable-next-line import/no-default-export
+export default BackgroundSessionIndicator;
diff --git a/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/index.tsx b/x-pack/plugins/data_enhanced/public/ui/background_session_indicator/index.tsx
new file mode 100644
index 0000000000000..2346d3343f114
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/public/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 LazyBackgroundSearchIndicator = React.lazy(() => import('./background_session_indicator'));
+export const BackgroundSearchIndicator = (props: BackgroundSessionIndicatorProps) => (
+ }>
+
+
+);
diff --git a/x-pack/plugins/data_enhanced/public/ui/background_session_state.ts b/x-pack/plugins/data_enhanced/public/ui/background_session_state.ts
new file mode 100644
index 0000000000000..b75c2a536f624
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/public/ui/background_session_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/ui/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/ui/connected_background_session_indicator.tsx
new file mode 100644
index 0000000000000..dc9c634e041e6
--- /dev/null
+++ b/x-pack/plugins/data_enhanced/public/ui/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 { BackgroundSearchIndicator } from './background_session_indicator';
+import { DataPublicPluginStart } from '../../../../../src/plugins/data/public/';
+import { BackgroundSessionViewState } from './background_session_state';
+
+export interface BackgroundSessionIndicatorDeps {
+ sessionService: DataPublicPluginStart['search']['session'];
+}
+
+export const createConnectedBackgroundSessionIndicator = ({
+ sessionService,
+}: BackgroundSessionIndicatorDeps): React.FC => {
+ const sessionId$ = sessionService.getSession$();
+ const isSession$ = sessionId$.pipe(
+ map((sessionId) => !!sessionId),
+ distinctUntilChanged()
+ );
+
+ return () => {
+ const isSession = useObservable(isSession$, !!sessionService.getSessionId());
+ if (!isSession) return null;
+ return ;
+ };
+};