Skip to content

Commit

Permalink
send to background UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Nov 2, 2020
1 parent 2653b90 commit 2b1274b
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/dev/storybook/aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/data_enhanced/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion x-pack/plugins/data_enhanced/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"optionalPlugins": ["kibanaUtils", "usageCollection"],
"server": true,
"ui": true,
"requiredBundles": ["kibanaUtils"]
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
13 changes: 12 additions & 1 deletion x-pack/plugins/data_enhanced/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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', () => (
<>
<div>
<BackgroundSessionIndicator state={BackgroundSessionViewState.Loading} />
</div>
<div>
<BackgroundSessionIndicator state={BackgroundSessionViewState.Completed} />
</div>
<div>
<BackgroundSessionIndicator state={BackgroundSessionViewState.BackgroundLoading} />
</div>
<div>
<BackgroundSessionIndicator state={BackgroundSessionViewState.BackgroundCompleted} />
</div>
<div>
<BackgroundSessionIndicator state={BackgroundSessionViewState.Restored} />
</div>
</>
));
Original file line number Diff line number Diff line change
@@ -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<EuiButtonIconProps, 'color' | 'iconType' | 'aria-label'> & { tooltipText: string };
popover: {
text: string;
buttons: Array<React.ComponentType<BackgroundSessionIndicatorProps>>;
};
};
} = {
[BackgroundSessionViewState.Loading]: {
button: {
color: 'subdued',
iconType: 'clock',
'aria-label': 'Loading results...',
tooltipText: 'Loading results',
},
popover: {
text: 'Loading',
buttons: [
({ onStopLoading = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onStopLoading} iconType={'cross'} flush={'both'}>
Cancel
</EuiButtonEmpty>
),
({ onContinueInBackground = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onContinueInBackground} flush={'both'}>
Continue in background
</EuiButtonEmpty>
),
],
},
},
[BackgroundSessionViewState.Completed]: {
button: {
color: 'subdued',
iconType: 'checkInCircleFilled',
'aria-label': 'Results loaded',
tooltipText: 'Results loaded',
},
popover: {
text: 'Results loaded',
buttons: [
({ onSaveResults = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onSaveResults} flush={'both'}>
Save results
</EuiButtonEmpty>
),
({ onViewBackgroundRequests = () => {} }) => (
// TODO: make this a link
<EuiButtonEmpty size="xs" onClick={onViewBackgroundRequests} flush={'both'}>
View requests
</EuiButtonEmpty>
),
],
},
},
[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 = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onStopLoading} iconType={'cross'} flush={'both'}>
Cancel
</EuiButtonEmpty>
),
({ onViewBackgroundRequests = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onViewBackgroundRequests} flush={'both'}>
View requests
</EuiButtonEmpty>
),
],
},
},
[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 = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onViewBackgroundRequests} flush={'both'}>
View background requests
</EuiButtonEmpty>
),
],
},
},
[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 = () => {} }) => (
<EuiButtonEmpty size="xs" onClick={onReload} iconType={'refresh'} flush={'both'}>
Reload
</EuiButtonEmpty>
),
],
},
},
};

export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProps> = (props) => {
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen);
const closePopover = () => setIsPopoverOpen(false);

const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state];

return (
<EuiPopover
ownFocus
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition={'leftCenter'}
button={
<EuiToolTip content={button.tooltipText}>
<EuiButtonIcon
color={button.color}
aria-label={button['aria-label']}
iconType={button.iconType}
onClick={onButtonClick}
/>
</EuiToolTip>
}
>
<EuiFlexGroup responsive={false} alignItems={'center'} gutterSize={'s'}>
<EuiFlexItem grow={true} style={{ marginRight: '12px' }}>
<EuiText size="s" color={'subdued'}>
<p>{popover.text}</p>
</EuiText>
</EuiFlexItem>
{popover.buttons.map((Button, index) => (
<EuiFlexItem key={props.state + index} grow={false}>
<Button {...props} />
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiPopover>
);
};

// React.lazy() needs default:
// eslint-disable-next-line import/no-default-export
export default BackgroundSessionIndicator;
Original file line number Diff line number Diff line change
@@ -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 = () => (
<EuiDelayRender>
<EuiLoadingSpinner />
</EuiDelayRender>
);

const LazyBackgroundSearchIndicator = React.lazy(() => import('./background_session_indicator'));
export const BackgroundSearchIndicator = (props: BackgroundSessionIndicatorProps) => (
<React.Suspense fallback={<Fallback />}>
<LazyBackgroundSearchIndicator {...props} />
</React.Suspense>
);
33 changes: 33 additions & 0 deletions x-pack/plugins/data_enhanced/public/ui/background_session_state.ts
Original file line number Diff line number Diff line change
@@ -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',
}
Original file line number Diff line number Diff line change
@@ -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 <BackgroundSearchIndicator state={BackgroundSessionViewState.Loading} />;
};
};

0 comments on commit 2b1274b

Please sign in to comment.