Skip to content

Commit

Permalink
[Logs UI] Add helper hooks with search strategy request cancellation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
weltenwort authored Dec 11, 2020
1 parent e5c7134 commit efe62ac
Show file tree
Hide file tree
Showing 26 changed files with 1,445 additions and 285 deletions.
7 changes: 6 additions & 1 deletion x-pack/plugins/infra/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

module.exports = require('@kbn/storybook').defaultConfig;
const defaultConfig = require('@kbn/storybook').defaultConfig;

module.exports = {
...defaultConfig,
stories: ['../**/*.stories.mdx', ...defaultConfig.stories],
};
22 changes: 19 additions & 3 deletions x-pack/plugins/infra/common/search_strategies/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@

import * as rt from 'io-ts';

const genericErrorRT = rt.type({
const abortedRequestSearchStrategyErrorRT = rt.type({
type: rt.literal('aborted'),
});

export type AbortedRequestSearchStrategyError = rt.TypeOf<
typeof abortedRequestSearchStrategyErrorRT
>;

const genericSearchStrategyErrorRT = rt.type({
type: rt.literal('generic'),
message: rt.string,
});

const shardFailureErrorRT = rt.type({
export type GenericSearchStrategyError = rt.TypeOf<typeof genericSearchStrategyErrorRT>;

const shardFailureSearchStrategyErrorRT = rt.type({
type: rt.literal('shardFailure'),
shardInfo: rt.type({
shard: rt.number,
Expand All @@ -21,6 +31,12 @@ const shardFailureErrorRT = rt.type({
message: rt.string,
});

export const searchStrategyErrorRT = rt.union([genericErrorRT, shardFailureErrorRT]);
export type ShardFailureSearchStrategyError = rt.TypeOf<typeof shardFailureSearchStrategyErrorRT>;

export const searchStrategyErrorRT = rt.union([
abortedRequestSearchStrategyErrorRT,
genericSearchStrategyErrorRT,
shardFailureSearchStrategyErrorRT,
]);

export type SearchStrategyError = rt.TypeOf<typeof searchStrategyErrorRT>;
25 changes: 25 additions & 0 deletions x-pack/plugins/infra/public/components/centered_flyout_body.tsx
Original file line number Diff line number Diff line change
@@ -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;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiFlyoutBody } from '@elastic/eui';
import { euiStyled } from '../../../observability/public';

export const CenteredEuiFlyoutBody = euiStyled(EuiFlyoutBody)`
& .euiFlyoutBody__overflow {
display: flex;
flex-direction: column;
}
& .euiFlyoutBody__overflowContent {
align-items: center;
align-self: stretch;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
overflow: hidden;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 { PropsOf } from '@elastic/eui';
import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import { EuiThemeProvider } from '../../../observability/public';
import { DataSearchErrorCallout } from './data_search_error_callout';

export default {
title: 'infra/dataSearch/DataSearchErrorCallout',
decorators: [
(wrappedStory) => (
<EuiThemeProvider>
<div style={{ width: 600 }}>{wrappedStory()}</div>
</EuiThemeProvider>
),
],
parameters: {
layout: 'padded',
},
argTypes: {
errors: {
control: {
type: 'object',
},
},
},
} as Meta;

type DataSearchErrorCalloutProps = PropsOf<typeof DataSearchErrorCallout>;

const DataSearchErrorCalloutTemplate: Story<DataSearchErrorCalloutProps> = (args) => (
<DataSearchErrorCallout {...args} />
);

const commonArgs = {
title: 'Failed to load data',
errors: [
{
type: 'generic' as const,
message: 'A generic error message',
},
{
type: 'shardFailure' as const,
shardInfo: {
index: 'filebeat-7.9.3-2020.12.01-000003',
node: 'a45hJUm3Tba4U8MkvkCU_g',
shard: 0,
},
message: 'No mapping found for [@timestamp] in order to sort on',
},
],
};

export const ErrorCallout = DataSearchErrorCalloutTemplate.bind({});

ErrorCallout.args = {
...commonArgs,
};

export const ErrorCalloutWithRetry = DataSearchErrorCalloutTemplate.bind({});

ErrorCalloutWithRetry.args = {
...commonArgs,
};
ErrorCalloutWithRetry.argTypes = {
onRetry: { action: 'retrying' },
};

export const AbortedErrorCallout = DataSearchErrorCalloutTemplate.bind({});

AbortedErrorCallout.args = {
...commonArgs,
errors: [
{
type: 'aborted',
},
],
};
AbortedErrorCallout.argTypes = {
onRetry: { action: 'retrying' },
};
Original file line number Diff line number Diff line change
@@ -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;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiButton, EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import {
AbortedRequestSearchStrategyError,
GenericSearchStrategyError,
SearchStrategyError,
ShardFailureSearchStrategyError,
} from '../../common/search_strategies/common/errors';

export const DataSearchErrorCallout: React.FC<{
title: React.ReactNode;
errors: SearchStrategyError[];
onRetry?: () => void;
}> = ({ errors, onRetry, title }) => {
const calloutColor = errors.some((error) => error.type !== 'aborted') ? 'danger' : 'warning';

return (
<EuiCallOut color={calloutColor} iconType="alert" title={title}>
{errors?.map((error, errorIndex) => (
<DataSearchErrorMessage key={errorIndex} error={error} />
))}
{onRetry ? (
<EuiButton color={calloutColor} size="s" onClick={onRetry}>
<FormattedMessage
id="xpack.infra.dataSearch.loadingErrorRetryButtonLabel"
defaultMessage="Retry"
/>
</EuiButton>
) : null}
</EuiCallOut>
);
};

const DataSearchErrorMessage: React.FC<{ error: SearchStrategyError }> = ({ error }) => {
if (error.type === 'aborted') {
return <AbortedRequestErrorMessage error={error} />;
} else if (error.type === 'shardFailure') {
return <ShardFailureErrorMessage error={error} />;
} else {
return <GenericErrorMessage error={error} />;
}
};

const AbortedRequestErrorMessage: React.FC<{
error?: AbortedRequestSearchStrategyError;
}> = ({}) => (
<FormattedMessage
tagName="p"
id="xpack.infra.dataSearch.abortedRequestErrorMessage"
defaultMessage="The request was aborted."
/>
);

const GenericErrorMessage: React.FC<{ error: GenericSearchStrategyError }> = ({ error }) => (
<p>{error.message ?? `${error}`}</p>
);

const ShardFailureErrorMessage: React.FC<{ error: ShardFailureSearchStrategyError }> = ({
error,
}) => (
<FormattedMessage
tagName="p"
id="xpack.infra.dataSearch.shardFailureErrorMessage"
defaultMessage="Index {indexName}: {errorMessage}"
values={{
indexName: error.shardInfo.index,
errorMessage: error.message,
}}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 { PropsOf } from '@elastic/eui';
import { Meta, Story } from '@storybook/react/types-6-0';
import React from 'react';
import { EuiThemeProvider } from '../../../observability/public';
import { DataSearchProgress } from './data_search_progress';

export default {
title: 'infra/dataSearch/DataSearchProgress',
decorators: [
(wrappedStory) => (
<EuiThemeProvider>
<div style={{ width: 400 }}>{wrappedStory()}</div>
</EuiThemeProvider>
),
],
parameters: {
layout: 'padded',
},
} as Meta;

type DataSearchProgressProps = PropsOf<typeof DataSearchProgress>;

const DataSearchProgressTemplate: Story<DataSearchProgressProps> = (args) => (
<DataSearchProgress {...args} />
);

export const UndeterminedProgress = DataSearchProgressTemplate.bind({});

export const DeterminedProgress = DataSearchProgressTemplate.bind({});

DeterminedProgress.args = {
label: 'Searching',
maxValue: 10,
value: 3,
};

export const CancelableDeterminedProgress = DataSearchProgressTemplate.bind({});

CancelableDeterminedProgress.args = {
label: 'Searching',
maxValue: 10,
value: 3,
};
CancelableDeterminedProgress.argTypes = {
onCancel: { action: 'canceled' },
};
45 changes: 45 additions & 0 deletions x-pack/plugins/infra/public/components/data_search_progress.tsx
Original file line number Diff line number Diff line change
@@ -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;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';

export const DataSearchProgress: React.FC<{
label?: React.ReactNode;
maxValue?: number;
onCancel?: () => void;
value?: number;
}> = ({ label, maxValue, onCancel, value }) => {
const valueText = useMemo(
() =>
Number.isFinite(maxValue) && Number.isFinite(value) ? `${value} / ${maxValue}` : undefined,
[value, maxValue]
);

return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiProgress label={label} size="s" max={maxValue} value={value} valueText={valueText} />
</EuiFlexItem>
{onCancel ? (
<EuiFlexItem grow={false}>
<EuiButtonIcon
color="danger"
iconType="cross"
onClick={onCancel}
title={cancelButtonLabel}
aria-label={cancelButtonLabel}
/>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
);
};

const cancelButtonLabel = i18n.translate('xpack.infra.dataSearch.cancelButtonLabel', {
defaultMessage: 'Cancel request',
});
Loading

0 comments on commit efe62ac

Please sign in to comment.