Flyout and alerts table improvements (#96976)
* Extend the TopAlert interface to include all the properties of the alert as well as the computed properties we add. Use these in the table and flyout. * Create a severity badge and use it in the table and flyout ![image](https://user-images.githubusercontent.com/9912/114796499-bc7d2b00-9d56-11eb-89fa-17c0240819ee.png) * Fix the query language toggle in the search bar ![image](https://user-images.githubusercontent.com/9912/114796507-c69f2980-9d56-11eb-8cfc-8432e4a71e77.png) # Table * Update status badges to match design ![image](https://user-images.githubusercontent.com/9912/114796518-ce5ece00-9d56-11eb-80a7-b8c8aa63d6b5.png) * Remove checkbox column * Make flyout open when clicking the reason * Change alert details link to a view in app link ![image](https://user-images.githubusercontent.com/9912/114796530-d74f9f80-9d56-11eb-951c-91e544c6e1d5.png) # Flyout * Add action button to flyout * Replace table on flyout with description list * Remove unused tabs on flyout * Add rule type to flyout heading * Add expected and actual value to the flyout ![image](https://user-images.githubusercontent.com/9912/114796550-e59dbb80-9d56-11eb-9825-25da979c3b22.png) Fixes #96907.
This commit is contained in:
parent
a90afbf1ec
commit
c67cda194f
11 changed files with 339 additions and 191 deletions
|
@ -16,18 +16,12 @@ import { PluginContext, PluginContextValue } from '../../context/plugin_context'
|
|||
import { createObservabilityRuleRegistryMock } from '../../rules/observability_rule_registry_mock';
|
||||
import { createCallObservabilityApi } from '../../services/call_observability_api';
|
||||
import type { ObservabilityAPIReturnType } from '../../services/call_observability_api/types';
|
||||
import { AlertsFlyout } from './alerts_flyout';
|
||||
import { TopAlert } from './alerts_table';
|
||||
import { apmAlertResponseExample, dynamicIndexPattern, flyoutItemExample } from './example_data';
|
||||
import { apmAlertResponseExample, dynamicIndexPattern } from './example_data';
|
||||
|
||||
interface PageArgs {
|
||||
items: ObservabilityAPIReturnType<'GET /api/observability/rules/alerts/top'>;
|
||||
}
|
||||
|
||||
interface FlyoutArgs {
|
||||
alert: TopAlert;
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'app/Alerts',
|
||||
component: AlertsPage,
|
||||
|
@ -95,8 +89,3 @@ export function EmptyState(_args: PageArgs) {
|
|||
return <AlertsPage routeParams={{ query: {} }} />;
|
||||
}
|
||||
EmptyState.args = { items: [] } as PageArgs;
|
||||
|
||||
export function Flyout({ alert }: FlyoutArgs) {
|
||||
return <AlertsFlyout alert={alert} onClose={() => {}} />;
|
||||
}
|
||||
Flyout.args = { alert: flyoutItemExample } as FlyoutArgs;
|
||||
|
|
|
@ -1,120 +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 {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutProps,
|
||||
EuiInMemoryTable,
|
||||
EuiSpacer,
|
||||
EuiTabbedContent,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { asDuration } from '../../../common/utils/formatters';
|
||||
import { TopAlert } from './alerts_table';
|
||||
|
||||
type AlertsFlyoutProps = { alert: TopAlert } & EuiFlyoutProps;
|
||||
|
||||
export function AlertsFlyout(props: AlertsFlyoutProps) {
|
||||
const { onClose, alert } = props;
|
||||
|
||||
const overviewListItems = [
|
||||
{
|
||||
title: 'Status',
|
||||
description: alert.active ? 'Active' : 'Recovered',
|
||||
},
|
||||
{
|
||||
title: 'Severity',
|
||||
description: alert.severityLevel || '-', // TODO: badge and "(changed 2 min ago)"
|
||||
},
|
||||
// {
|
||||
// title: 'Affected entity',
|
||||
// description: affectedEntity || '-', // TODO: link to entity
|
||||
// },
|
||||
{
|
||||
title: 'Triggered',
|
||||
description: alert.start, // TODO: format date
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
description: asDuration(alert.duration, { extended: true }) || '-', // TODO: format duration
|
||||
},
|
||||
// {
|
||||
// title: 'Expected value',
|
||||
// description: expectedValue || '-',
|
||||
// },
|
||||
// {
|
||||
// title: 'Actual value',
|
||||
// description: actualValue || '-',
|
||||
// },
|
||||
{
|
||||
title: 'Rule type',
|
||||
description: alert.ruleCategory || '-',
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'overview',
|
||||
name: i18n.translate('xpack.observability.alerts.flyoutOverviewTabTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiInMemoryTable
|
||||
columns={[
|
||||
{ field: 'title', name: '' },
|
||||
{ field: 'description', name: '' },
|
||||
]}
|
||||
items={overviewListItems}
|
||||
/>
|
||||
{/* <EuiSpacer />
|
||||
<EuiTitle size="xs">
|
||||
<h4>Severity log</h4>
|
||||
</EuiTitle>
|
||||
<EuiInMemoryTable
|
||||
columns={[
|
||||
{ field: '@timestamp', name: 'Timestamp', dataType: 'date' },
|
||||
{
|
||||
field: 'severity',
|
||||
name: 'Severity',
|
||||
render: (_, item) => (
|
||||
<>
|
||||
<EuiBadge>{item.severity}</EuiBadge> {item.message}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
items={severityLog ?? []}
|
||||
/> */}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'metadata',
|
||||
name: i18n.translate('xpack.observability.alerts.flyoutMetadataTabTitle', {
|
||||
defaultMessage: 'Metadata',
|
||||
}),
|
||||
disabled: true,
|
||||
content: <></>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="s">
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="xs">
|
||||
<h2>{alert.ruleName}</h2>
|
||||
</EuiTitle>
|
||||
<EuiTabbedContent size="s" tabs={tabs} />
|
||||
</EuiFlyoutHeader>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { PluginContext, PluginContextValue } from '../../../context/plugin_context';
|
||||
import { TopAlert } from '../';
|
||||
import { AlertsFlyout } from './';
|
||||
|
||||
interface Args {
|
||||
alert: TopAlert;
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'app/Alerts/AlertsFlyout',
|
||||
component: AlertsFlyout,
|
||||
decorators: [
|
||||
(Story: ComponentType) => {
|
||||
return (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
docLinks: { links: { query: {} } },
|
||||
storage: { get: () => {} },
|
||||
uiSettings: {
|
||||
get: (setting: string) => {
|
||||
if (setting === 'dateFormat') {
|
||||
return 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
}
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<PluginContext.Provider
|
||||
value={
|
||||
({
|
||||
core: {
|
||||
http: { basePath: { prepend: (_: string) => '' } },
|
||||
},
|
||||
} as unknown) as PluginContextValue
|
||||
}
|
||||
>
|
||||
<Story />
|
||||
</PluginContext.Provider>
|
||||
</KibanaContextProvider>
|
||||
//
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function Example({ alert }: Args) {
|
||||
return <AlertsFlyout alert={alert} onClose={() => {}} />;
|
||||
}
|
||||
Example.args = {
|
||||
alert: {
|
||||
link: '/app/apm/services/opbeans-java?rangeFrom=now-15m&rangeTo=now',
|
||||
reason: 'Error count for opbeans-java was above the threshold',
|
||||
active: true,
|
||||
start: 1618235449493,
|
||||
|
||||
'rule.id': 'apm.error_rate',
|
||||
'service.environment': 'production',
|
||||
'service.name': 'opbeans-java',
|
||||
'rule.name': 'Error count threshold | opbeans-java (smith test)',
|
||||
'kibana.rac.alert.duration.us': 61787000,
|
||||
'kibana.observability.evaluation.threshold': 0,
|
||||
'kibana.rac.alert.status': 'open',
|
||||
tags: ['apm', 'service.name:opbeans-java'],
|
||||
'kibana.rac.alert.uuid': 'c50fbc70-0d77-462d-ac0a-f2bd0b8512e4',
|
||||
'rule.uuid': '474920d0-93e9-11eb-ac86-0b455460de81',
|
||||
'event.action': 'active',
|
||||
'@timestamp': '2021-04-14T21:43:42.966Z',
|
||||
'kibana.rac.alert.id': 'apm.error_rate_opbeans-java_production',
|
||||
'processor.event': 'error',
|
||||
'kibana.rac.alert.start': '2021-04-14T21:42:41.179Z',
|
||||
'kibana.rac.producer': 'apm',
|
||||
'event.kind': 'state',
|
||||
'rule.category': 'Error count threshold',
|
||||
'kibana.observability.evaluation.value': 1,
|
||||
},
|
||||
} as Args;
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlyoutProps,
|
||||
EuiSpacer,
|
||||
EuiTabbedContent,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment-timezone';
|
||||
import React from 'react';
|
||||
import { useUiSetting } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { asDuration } from '../../../../common/utils/formatters';
|
||||
import { usePluginContext } from '../../../hooks/use_plugin_context';
|
||||
import { TopAlert } from '../';
|
||||
import { SeverityBadge } from '../severity_badge';
|
||||
|
||||
type AlertsFlyoutProps = { alert: TopAlert } & EuiFlyoutProps;
|
||||
|
||||
export function AlertsFlyout({ onClose, alert }: AlertsFlyoutProps) {
|
||||
const dateFormat = useUiSetting<string>('dateFormat');
|
||||
const { core } = usePluginContext();
|
||||
const { prepend } = core.http.basePath;
|
||||
|
||||
const overviewListItems = [
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.statusLabel', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
description: alert.active ? 'Active' : 'Recovered',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.severityLabel', {
|
||||
defaultMessage: 'Severity',
|
||||
}),
|
||||
description: <SeverityBadge severityLevel={alert['kibana.rac.alert.severity.level']} />,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.triggeredLabel', {
|
||||
defaultMessage: 'Triggered',
|
||||
}),
|
||||
description: (
|
||||
<span title={alert.start.toString()}>{moment(alert.start).format(dateFormat)}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.durationLabel', {
|
||||
defaultMessage: 'Duration',
|
||||
}),
|
||||
description: asDuration(alert['kibana.rac.alert.duration.us'], { extended: true }),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.expectedValueLabel', {
|
||||
defaultMessage: 'Expected value',
|
||||
}),
|
||||
description: alert['kibana.observability.evaluation.threshold'] ?? '-',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.actualValueLabel', {
|
||||
defaultMessage: 'Actual value',
|
||||
}),
|
||||
description: alert['kibana.observability.evaluation.value'] ?? '-',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.alertsFlyout.ruleTypeLabel', {
|
||||
defaultMessage: 'Rule type',
|
||||
}),
|
||||
description: alert['rule.category'] ?? '-',
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'overview',
|
||||
name: i18n.translate('xpack.observability.alerts.flyoutOverviewTabTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiDescriptionList type="responsiveColumn" listItems={overviewListItems} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="m">
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2>{alert['rule.name']}</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s">{alert.reason}</EuiText>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiTabbedContent size="s" tabs={tabs} />
|
||||
</EuiFlyoutBody>
|
||||
{alert.link && (
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton href={prepend(alert.link)} fill>
|
||||
View in app
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
)}
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { SearchBar, TimeHistory } from '../../../../../../src/plugins/data/public';
|
||||
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { useFetcher } from '../../hooks/use_fetcher';
|
||||
|
@ -29,6 +29,7 @@ export function AlertsSearchBar({
|
|||
const timeHistory = useMemo(() => {
|
||||
return new TimeHistory(new Storage(localStorage));
|
||||
}, []);
|
||||
const [queryLanguage, setQueryLanguage] = useState<'lucene' | 'kuery'>('kuery');
|
||||
|
||||
const { data: dynamicIndexPattern } = useFetcher(({ signal }) => {
|
||||
return callObservabilityApi({
|
||||
|
@ -43,7 +44,7 @@ export function AlertsSearchBar({
|
|||
placeholder={i18n.translate('xpack.observability.alerts.searchBarPlaceholder', {
|
||||
defaultMessage: '"domain": "ecommerce" AND ("service.name": "ProductCatalogService" …)',
|
||||
})}
|
||||
query={{ query: query ?? '', language: 'kuery' }}
|
||||
query={{ query: query ?? '', language: queryLanguage }}
|
||||
timeHistory={timeHistory}
|
||||
dateRangeFrom={rangeFrom}
|
||||
dateRangeTo={rangeTo}
|
||||
|
@ -55,6 +56,7 @@ export function AlertsSearchBar({
|
|||
dateRange,
|
||||
query: typeof nextQuery?.query === 'string' ? nextQuery.query : '',
|
||||
});
|
||||
setQueryLanguage((nextQuery?.language || 'kuery') as 'kuery' | 'lucene');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -6,31 +6,22 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
CustomItemAction,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiBasicTableProps,
|
||||
DefaultItemAction,
|
||||
EuiTableSelectionType,
|
||||
EuiButton,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import { asDuration } from '../../../common/utils/formatters';
|
||||
import { TimestampTooltip } from '../../components/shared/timestamp_tooltip';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import type { TopAlert } from './';
|
||||
import { AlertsFlyout } from './alerts_flyout';
|
||||
|
||||
export interface TopAlert {
|
||||
start: number;
|
||||
duration: number;
|
||||
reason: string;
|
||||
link?: string;
|
||||
severityLevel?: string;
|
||||
active: boolean;
|
||||
ruleName: string;
|
||||
ruleCategory: string;
|
||||
}
|
||||
import { SeverityBadge } from './severity_badge';
|
||||
|
||||
type AlertsTableProps = Omit<
|
||||
EuiBasicTableProps<TopAlert>,
|
||||
|
@ -43,13 +34,18 @@ export function AlertsTable(props: AlertsTableProps) {
|
|||
const { core } = usePluginContext();
|
||||
const { prepend } = core.http.basePath;
|
||||
|
||||
const actions: Array<DefaultItemAction<TopAlert>> = [
|
||||
const actions: Array<CustomItemAction<TopAlert>> = [
|
||||
{
|
||||
name: 'Alert details',
|
||||
description: 'Alert details',
|
||||
onClick: (item) => {
|
||||
setFlyoutAlert(item);
|
||||
},
|
||||
render: (alert) =>
|
||||
alert.link ? (
|
||||
<EuiButton href={prepend(alert.link)} size="s">
|
||||
{i18n.translate('xpack.observability.alertsTable.viewInAppButtonLabel', {
|
||||
defaultMessage: 'View in app',
|
||||
})}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
isPrimary: true,
|
||||
},
|
||||
];
|
||||
|
@ -57,54 +53,76 @@ export function AlertsTable(props: AlertsTableProps) {
|
|||
const columns: Array<EuiBasicTableColumn<TopAlert>> = [
|
||||
{
|
||||
field: 'active',
|
||||
name: 'Status',
|
||||
width: '112px',
|
||||
render: (_, { active }) => {
|
||||
const style = {
|
||||
width: '96px',
|
||||
textAlign: 'center' as const,
|
||||
};
|
||||
name: i18n.translate('xpack.observability.alertsTable.statusColumnDescription', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
align: 'center',
|
||||
render: (_, alert) => {
|
||||
const { active } = alert;
|
||||
|
||||
return active ? (
|
||||
<EuiBadge iconType="alert" color="danger" style={style}>
|
||||
{i18n.translate('xpack.observability.alertsTable.status.active', {
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.observability.alertsTable.statusActiveDescription', {
|
||||
defaultMessage: 'Active',
|
||||
})}
|
||||
</EuiBadge>
|
||||
color="danger"
|
||||
type="alert"
|
||||
/>
|
||||
) : (
|
||||
<EuiBadge iconType="check" color="hollow" style={style}>
|
||||
{i18n.translate('xpack.observability.alertsTable.status.recovered', {
|
||||
<EuiIconTip
|
||||
content={i18n.translate('xpack.observability.alertsTable.statusRecoveredDescription', {
|
||||
defaultMessage: 'Recovered',
|
||||
})}
|
||||
</EuiBadge>
|
||||
type="check"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'start',
|
||||
name: 'Triggered',
|
||||
name: i18n.translate('xpack.observability.alertsTable.triggeredColumnDescription', {
|
||||
defaultMessage: 'Triggered',
|
||||
}),
|
||||
render: (_, item) => {
|
||||
return <TimestampTooltip time={new Date(item.start).getTime()} timeUnit="milliseconds" />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'duration',
|
||||
name: 'Duration',
|
||||
render: (_, { duration, active }) => {
|
||||
return active ? null : asDuration(duration, { extended: true });
|
||||
name: i18n.translate('xpack.observability.alertsTable.durationColumnDescription', {
|
||||
defaultMessage: 'Duration',
|
||||
}),
|
||||
render: (_, alert) => {
|
||||
const { active } = alert;
|
||||
return active
|
||||
? null
|
||||
: asDuration(alert['kibana.rac.alert.duration.us'], { extended: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'severity',
|
||||
name: i18n.translate('xpack.observability.alertsTable.severityColumnDescription', {
|
||||
defaultMessage: 'Severity',
|
||||
}),
|
||||
render: (_, alert) => {
|
||||
return <SeverityBadge severityLevel={alert['kibana.rac.alert.severity.level']} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'reason',
|
||||
name: 'Reason',
|
||||
name: i18n.translate('xpack.observability.alertsTable.reasonColumnDescription', {
|
||||
defaultMessage: 'Reason',
|
||||
}),
|
||||
dataType: 'string',
|
||||
render: (_, item) => {
|
||||
return item.link ? <EuiLink href={prepend(item.link)}>{item.reason}</EuiLink> : item.reason;
|
||||
return <EuiLink onClick={() => setFlyoutAlert(item)}>{item.reason}</EuiLink>;
|
||||
},
|
||||
},
|
||||
{
|
||||
actions,
|
||||
name: 'Actions',
|
||||
name: i18n.translate('xpack.observability.alertsTable.actionsColumnDescription', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -113,8 +131,6 @@ export function AlertsTable(props: AlertsTableProps) {
|
|||
{flyoutAlert && <AlertsFlyout alert={flyoutAlert} onClose={handleFlyoutClose} />}
|
||||
<EuiBasicTable<TopAlert>
|
||||
{...props}
|
||||
isSelectable={true}
|
||||
selection={{} as EuiTableSelectionType<TopAlert>}
|
||||
columns={columns}
|
||||
tableLayout="auto"
|
||||
pagination={{ pageIndex: 0, pageSize: 0, totalItemCount: 0 }}
|
||||
|
|
|
@ -12,6 +12,7 @@ export const apmAlertResponseExample = [
|
|||
'rule.name': 'Error count threshold | opbeans-java (smith test)',
|
||||
'kibana.rac.alert.duration.us': 180057000,
|
||||
'kibana.rac.alert.status': 'open',
|
||||
'kibana.rac.alert.severity.level': 'warning',
|
||||
tags: ['apm', 'service.name:opbeans-java'],
|
||||
'kibana.rac.alert.uuid': '0175ec0a-a3b1-4d41-b557-e21c2d024352',
|
||||
'rule.uuid': '474920d0-93e9-11eb-ac86-0b455460de81',
|
||||
|
@ -47,16 +48,6 @@ export const apmAlertResponseExample = [
|
|||
},
|
||||
];
|
||||
|
||||
export const flyoutItemExample = {
|
||||
link: '/app/apm/services/opbeans-java?rangeFrom=now-15m&rangeTo=now',
|
||||
reason: 'Error count for opbeans-java was above the threshold',
|
||||
active: true,
|
||||
start: 1618235449493,
|
||||
duration: 180057000,
|
||||
ruleCategory: 'Error count threshold',
|
||||
ruleName: 'Error count threshold | opbeans-java (smith test)',
|
||||
};
|
||||
|
||||
export const dynamicIndexPattern = {
|
||||
fields: [
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { format, parse } from 'url';
|
||||
import type { ObservabilityAPIReturnType } from '../../services/call_observability_api/types';
|
||||
import { ExperimentalBadge } from '../../components/shared/experimental_badge';
|
||||
import { useFetcher } from '../../hooks/use_fetcher';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
|
@ -28,6 +29,15 @@ import { asDuration, asPercent } from '../../../common/utils/formatters';
|
|||
import { AlertsSearchBar } from './alerts_search_bar';
|
||||
import { AlertsTable } from './alerts_table';
|
||||
|
||||
export type TopAlertResponse = ObservabilityAPIReturnType<'GET /api/observability/rules/alerts/top'>[number];
|
||||
|
||||
export interface TopAlert extends TopAlertResponse {
|
||||
start: number;
|
||||
reason: string;
|
||||
link?: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
interface AlertsPageProps {
|
||||
routeParams: RouteParams<'/alerts'>;
|
||||
}
|
||||
|
@ -75,6 +85,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
|
|||
const parsedLink = formatted.link ? parse(formatted.link, true) : undefined;
|
||||
|
||||
return {
|
||||
...alert,
|
||||
...formatted,
|
||||
link: parsedLink
|
||||
? format({
|
||||
|
@ -87,11 +98,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
|
|||
})
|
||||
: undefined,
|
||||
active: alert['event.action'] !== 'close',
|
||||
severityLevel: alert['kibana.rac.alert.severity.level'],
|
||||
start: new Date(alert['kibana.rac.alert.start']).getTime(),
|
||||
duration: alert['kibana.rac.alert.duration.us'],
|
||||
ruleCategory: alert['rule.category'],
|
||||
ruleName: alert['rule.name'],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 React, { ComponentProps } from 'react';
|
||||
import { SeverityBadge } from './severity_badge';
|
||||
|
||||
type Args = ComponentProps<typeof SeverityBadge>;
|
||||
|
||||
export default {
|
||||
title: 'app/Alerts/SeverityBadge',
|
||||
component: SeverityBadge,
|
||||
};
|
||||
|
||||
export function Example({ severityLevel }: Args) {
|
||||
return <SeverityBadge severityLevel={severityLevel} />;
|
||||
}
|
||||
Example.args = { severityLevel: 'critical' } as Args;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
export interface SeverityBadgeProps {
|
||||
severityLevel?: string;
|
||||
}
|
||||
|
||||
const colorMap: { [key: string]: string } = {
|
||||
critical: 'danger',
|
||||
warning: 'warning',
|
||||
};
|
||||
|
||||
export function SeverityBadge({ severityLevel }: SeverityBadgeProps) {
|
||||
return (
|
||||
<EuiBadge color={severityLevel ? colorMap[severityLevel] : 'default'}>
|
||||
{severityLevel ??
|
||||
i18n.translate('xpack.observability.severityBadge.unknownDescription', {
|
||||
defaultMessage: 'unknown',
|
||||
})}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
|
@ -9,7 +9,7 @@ import { ObservabilityRuleRegistry } from '../plugin';
|
|||
|
||||
const createRuleRegistryMock = () => ({
|
||||
registerType: () => {},
|
||||
getTypeByRuleId: () => {},
|
||||
getTypeByRuleId: () => ({ format: () => ({ link: '/test/example' }) }),
|
||||
create: () => createRuleRegistryMock(),
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue