mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[AO] Implement Alert Summary Widget new design (#149348)
Closes #149239
Closes #149238
## 📝 Summary
This PR implements the new design of the Alert Summary Widget. In the
new design, we removed the recovered chart and count to make it easier
to understand.
([design](https://www.figma.com/file/xnSsLoEMntX3VLG0qwmmnt/Alert-summary-widget-V2?t=m2Xd16Obz5OJz7A1-0))
After discussion with @maciejforcone, we decided to use blue color
instead of black since when we have one alert that triggers the whole
time during the selected period, the chart was not readable.
|Full-size|Compact|
|---|---|
||
https://user-images.githubusercontent.com/12370520/214258610-2d5d0b9b-9034-4cec-885f-c57959cd7d53.mov
## 🧪 How to test
- Check the component's new design in
[storybook](https://ci-artifacts.kibana.dev/storybooks/pr-149348/5181917c254d5a4e6038be7ceb5e551fcab03161/triggers_actions_ui/index.html?path=/story/app-alertsummarywidget--compact)
- Generate some alerts
- Check the Alerts page; you should see the Alert Summary Widget show
the correct data
- Check the Rule details of one of the alerts to see the compact version
there
- Clicking on Alert Summary Widget compact version should work as
expected
Co-authored-by: Katrin Freihofner <katrin.freihofner@elastic.co>
This commit is contained in:
parent
0d613e58cf
commit
7bce7c934d
21 changed files with 282 additions and 322 deletions
|
@ -35086,10 +35086,8 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.start": "Démarrer",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.status": "Statut",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "La durée dépasse le temps d'exécution attendu de la règle.",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "Actuellement actives",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingBody": "Une erreur s'est produite lors du chargement du récapitulatif des alertes. Contactez votre administrateur pour obtenir de l'aide.",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingTitle": "Impossible de charger le récapitulatif des alertes",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "Alertes",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "Supprimer la règle",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "Désactiver",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.editRuleButtonLabel": "Modifier",
|
||||
|
@ -35124,7 +35122,6 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.redirectObjectNoun": "règle",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText": "Alertes",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText": "Historique",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.ruleSummary.recoveredLabel": "Récupéré",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.statusPanel.ruleIsEnabledDisabledTitle": "La règle est",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.actionErrors": "Actions comportant des erreurs",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.close": "Fermer",
|
||||
|
|
|
@ -35054,10 +35054,8 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.start": "開始",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.status": "ステータス",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "期間がルールの想定実行時間を超えています。",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "現在アクティブ",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingBody": "アラート概要の読み込みエラーが発生しました。ヘルプについては、管理者にお問い合わせください。",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingTitle": "アラート概要を読み込めません",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "アラート",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "ルールの削除",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "無効にする",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.editRuleButtonLabel": "編集",
|
||||
|
@ -35092,7 +35090,6 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.redirectObjectNoun": "ルール",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText": "アラート",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText": "履歴",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.ruleSummary.recoveredLabel": "回復済み",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.statusPanel.ruleIsEnabledDisabledTitle": "ルールは",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.actionErrors": "エラーのアクション",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.close": "閉じる",
|
||||
|
|
|
@ -35091,10 +35091,8 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.start": "启动",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.columns.status": "状态",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsList.ruleTypeExcessDurationMessage": "持续时间超出了规则的预期运行时间。",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel": "当前处于活动状态",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingBody": "加载告警摘要时出现错误。请联系您的管理员寻求帮助。",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingTitle": "无法加载告警摘要",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "告警",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "删除规则",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "禁用",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.editRuleButtonLabel": "编辑",
|
||||
|
@ -35129,7 +35127,6 @@
|
|||
"xpack.triggersActionsUI.sections.ruleDetails.redirectObjectNoun": "规则",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.alertsTabText": "告警",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.eventLogTabText": "历史记录",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.ruleSummary.recoveredLabel": "已恢复",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.rule.statusPanel.ruleIsEnabledDisabledTitle": "规则为",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.actionErrors": "错误操作",
|
||||
"xpack.triggersActionsUI.sections.ruleDetails.ruleActionErrorLogFlyout.close": "关闭",
|
||||
|
|
|
@ -46,7 +46,6 @@ describe('useLoadAlertSummary', () => {
|
|||
activeAlertCount: 0,
|
||||
activeAlerts: [],
|
||||
recoveredAlertCount: 0,
|
||||
recoveredAlerts: [],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ interface AlertSummary {
|
|||
activeAlertCount: number;
|
||||
activeAlerts: Alert[];
|
||||
recoveredAlertCount: number;
|
||||
recoveredAlerts: Alert[];
|
||||
}
|
||||
|
||||
interface LoadAlertSummaryResponse {
|
||||
|
@ -44,7 +43,6 @@ export function useLoadAlertSummary({ featureIds, timeRange, filter }: UseLoadAl
|
|||
activeAlertCount: 0,
|
||||
activeAlerts: [],
|
||||
recoveredAlertCount: 0,
|
||||
recoveredAlerts: [],
|
||||
},
|
||||
});
|
||||
const isCancelledRef = useRef(false);
|
||||
|
@ -56,14 +54,13 @@ export function useLoadAlertSummary({ featureIds, timeRange, filter }: UseLoadAl
|
|||
abortCtrlRef.current = new AbortController();
|
||||
|
||||
try {
|
||||
const { activeAlertCount, activeAlerts, recoveredAlertCount, recoveredAlerts } =
|
||||
await fetchAlertSummary({
|
||||
featureIds,
|
||||
filter,
|
||||
http,
|
||||
signal: abortCtrlRef.current.signal,
|
||||
timeRange,
|
||||
});
|
||||
const { activeAlertCount, activeAlerts, recoveredAlertCount } = await fetchAlertSummary({
|
||||
featureIds,
|
||||
filter,
|
||||
http,
|
||||
signal: abortCtrlRef.current.signal,
|
||||
timeRange,
|
||||
});
|
||||
|
||||
if (!isCancelledRef.current) {
|
||||
setAlertSummary(() => ({
|
||||
|
@ -71,7 +68,6 @@ export function useLoadAlertSummary({ featureIds, timeRange, filter }: UseLoadAl
|
|||
activeAlertCount,
|
||||
activeAlerts,
|
||||
recoveredAlertCount,
|
||||
recoveredAlerts,
|
||||
},
|
||||
isLoading: false,
|
||||
}));
|
||||
|
@ -123,12 +119,10 @@ async function fetchAlertSummary({
|
|||
const activeAlertCount = res?.activeAlertCount ?? 0;
|
||||
const activeAlerts = res?.activeAlerts ?? [];
|
||||
const recoveredAlertCount = res?.recoveredAlertCount ?? 0;
|
||||
const recoveredAlerts = res?.recoveredAlerts ?? [];
|
||||
|
||||
return {
|
||||
activeAlertCount,
|
||||
activeAlerts,
|
||||
recoveredAlertCount,
|
||||
recoveredAlerts,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,32 +25,14 @@ export const mockedAlertSummaryResponse = {
|
|||
{ key: 1671808000000, doc_count: 6 },
|
||||
{ key: 1671908000000, doc_count: 14 },
|
||||
{ key: 1672008000000, doc_count: 15 },
|
||||
{ key: 1672108000000, doc_count: 15 },
|
||||
{ key: 1672108000000, doc_count: 20 },
|
||||
{ key: 1672208000000, doc_count: 10 },
|
||||
{ key: 1672308000000, doc_count: 9 },
|
||||
{ key: 1672408000000, doc_count: 7 },
|
||||
{ key: 1672508000000, doc_count: 2 },
|
||||
{ key: 1672608000000, doc_count: 2 },
|
||||
],
|
||||
recoveredAlertCount: 15,
|
||||
recoveredAlerts: [
|
||||
{ key: 1671108000000, doc_count: 0 },
|
||||
{ key: 1671208000000, doc_count: 0 },
|
||||
{ key: 1671308000000, doc_count: 0 },
|
||||
{ key: 1671408000000, doc_count: 0 },
|
||||
{ key: 1671508000000, doc_count: 0 },
|
||||
{ key: 1671608000000, doc_count: 0 },
|
||||
{ key: 1671708000000, doc_count: 2 },
|
||||
{ key: 1671808000000, doc_count: 0 },
|
||||
{ key: 1671908000000, doc_count: 0 },
|
||||
{ key: 1672008000000, doc_count: 0 },
|
||||
{ key: 1672108000000, doc_count: 0 },
|
||||
{ key: 1672208000000, doc_count: 5 },
|
||||
{ key: 1672308000000, doc_count: 1 },
|
||||
{ key: 1672408000000, doc_count: 2 },
|
||||
{ key: 1672508000000, doc_count: 5 },
|
||||
{ key: 1672608000000, doc_count: 0 },
|
||||
],
|
||||
recoveredAlertCount: 20,
|
||||
};
|
||||
|
||||
export const mockedAlertSummaryTimeRange: AlertSummaryTimeRange = {
|
||||
|
|
|
@ -31,10 +31,6 @@ jest.mock('../../../../hooks/use_load_alert_summary', () => ({
|
|||
{ key: 1671321600000, doc_count: 0 },
|
||||
{ key: 1671408000000, doc_count: 1 },
|
||||
],
|
||||
recoveredAlerts: [
|
||||
{ key: 1671321600000, doc_count: 2 },
|
||||
{ key: 1671408000000, doc_count: 5 },
|
||||
],
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
@ -74,7 +70,6 @@ describe('AlertSummaryWidget', () => {
|
|||
it('should render counts and title correctly', async () => {
|
||||
const alertSummaryWidget = renderComponent();
|
||||
expect(alertSummaryWidget.queryByTestId('activeAlertsCount')).toHaveTextContent('1');
|
||||
expect(alertSummaryWidget.queryByTestId('recoveredAlertsCount')).toHaveTextContent('7');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('8');
|
||||
expect(alertSummaryWidget.queryByTestId(TITLE_DATA_TEST_SUBJ)).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ export const AlertSummaryWidget = ({
|
|||
chartThemes,
|
||||
}: AlertSummaryWidgetProps) => {
|
||||
const {
|
||||
alertSummary: { activeAlertCount, activeAlerts, recoveredAlertCount, recoveredAlerts },
|
||||
alertSummary: { activeAlertCount, activeAlerts, recoveredAlertCount },
|
||||
isLoading,
|
||||
error,
|
||||
} = useLoadAlertSummary({
|
||||
|
@ -41,7 +41,6 @@ export const AlertSummaryWidget = ({
|
|||
activeAlertCount={activeAlertCount}
|
||||
activeAlerts={activeAlerts}
|
||||
recoveredAlertCount={recoveredAlertCount}
|
||||
recoveredAlerts={recoveredAlerts}
|
||||
dateFormat={timeRange.dateFormat}
|
||||
chartThemes={chartThemes}
|
||||
/>
|
||||
|
@ -51,7 +50,6 @@ export const AlertSummaryWidget = ({
|
|||
activeAlerts={activeAlerts}
|
||||
onClick={onClick}
|
||||
recoveredAlertCount={recoveredAlertCount}
|
||||
recoveredAlerts={recoveredAlerts}
|
||||
timeRangeTitle={timeRange.title}
|
||||
chartThemes={chartThemes}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { EuiIcon, EuiText, useEuiTheme } from '@elastic/eui';
|
||||
import { ACTIVE_ALERT_LABEL, ALERT_COUNT_FORMAT } from './constants';
|
||||
|
||||
interface Props {
|
||||
activeAlertCount: number;
|
||||
}
|
||||
|
||||
export const ActiveAlertCounts = ({ activeAlertCount }: Props) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText
|
||||
color={!!activeAlertCount ? euiTheme.colors.dangerText : euiTheme.colors.successText}
|
||||
>
|
||||
<h3 data-test-subj={`activeAlertsCount`}>
|
||||
{numeral(activeAlertCount).format(ALERT_COUNT_FORMAT)}
|
||||
{!!activeAlertCount && (
|
||||
<>
|
||||
|
||||
<EuiIcon type="alert" ascent={10} />
|
||||
</>
|
||||
)}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiText size="s" color="subdued">
|
||||
{ACTIVE_ALERT_LABEL}
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { MouseEvent } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
|
||||
import { ALERT_STATUS_ACTIVE, AlertStatus } from '@kbn/rule-data-utils';
|
||||
import { ActiveAlertCounts } from './active_alert_counts';
|
||||
import { AllAlertCounts } from './all_alert_counts';
|
||||
|
||||
interface Props {
|
||||
activeAlertCount: number;
|
||||
recoveredAlertCount: number;
|
||||
onActiveClick?: (
|
||||
event: MouseEvent<HTMLAnchorElement | HTMLDivElement>,
|
||||
status?: AlertStatus
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const AlertCounts = ({ activeAlertCount, recoveredAlertCount, onActiveClick }: Props) => {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="l" responsive={false}>
|
||||
<EuiFlexItem style={{ minWidth: 50, wordWrap: 'break-word' }} grow={false}>
|
||||
<AllAlertCounts count={activeAlertCount + recoveredAlertCount} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ minWidth: 50, wordWrap: 'break-word' }} grow={false}>
|
||||
{!!onActiveClick ? (
|
||||
<EuiLink
|
||||
onClick={(event: React.MouseEvent<HTMLAnchorElement>) =>
|
||||
onActiveClick(event, ALERT_STATUS_ACTIVE)
|
||||
}
|
||||
data-test-subj="activeAlerts"
|
||||
>
|
||||
<ActiveAlertCounts activeAlertCount={activeAlertCount} />
|
||||
</EuiLink>
|
||||
) : (
|
||||
<ActiveAlertCounts activeAlertCount={activeAlertCount} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -1,96 +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 { Axis, Chart, CurveType, LineSeries, Position, ScaleType, Settings } from '@elastic/charts';
|
||||
import { Color } from '@elastic/charts/dist/common/colors';
|
||||
import { ColorVariant } from '@elastic/charts/dist/utils/common';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiListGroupItemProps,
|
||||
EuiText,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { EUI_SPARKLINE_THEME_PARTIAL } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import React from 'react';
|
||||
import { Alert, ChartThemes } from '../types';
|
||||
|
||||
interface AlertStateInfoProps {
|
||||
chartThemes: ChartThemes;
|
||||
count: number;
|
||||
data: Alert[];
|
||||
dataTestSubj: string;
|
||||
domain: { min: number; max: number };
|
||||
id: string;
|
||||
stroke: Color | ColorVariant;
|
||||
title: EuiListGroupItemProps['label'];
|
||||
}
|
||||
|
||||
export const AlertStateInfo = ({
|
||||
count,
|
||||
data,
|
||||
dataTestSubj,
|
||||
domain,
|
||||
id,
|
||||
stroke,
|
||||
chartThemes: { theme, baseTheme },
|
||||
title,
|
||||
}: AlertStateInfoProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const chartTheme = [
|
||||
theme,
|
||||
EUI_SPARKLINE_THEME_PARTIAL,
|
||||
{
|
||||
chartMargins: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={1} style={{ minWidth: '70px' }}>
|
||||
<EuiText color={euiTheme.colors.text}>
|
||||
<h3 data-test-subj={`${dataTestSubj}Count`}>{count}</h3>
|
||||
</EuiText>
|
||||
<EuiText size="s" color="subdued">
|
||||
{title}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<Chart size={{ height: 50 }}>
|
||||
<Settings theme={chartTheme} baseTheme={baseTheme} tooltip={{ type: 'none' }} />
|
||||
<Axis
|
||||
domain={domain}
|
||||
hide
|
||||
id={id + '-axis'}
|
||||
position={Position.Left}
|
||||
showGridLines={false}
|
||||
/>
|
||||
<LineSeries
|
||||
id={id}
|
||||
xScaleType={ScaleType.Time}
|
||||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
data={data}
|
||||
lineSeriesStyle={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
stroke,
|
||||
},
|
||||
}}
|
||||
curve={CurveType.CURVE_MONOTONE_X}
|
||||
/>
|
||||
</Chart>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -14,7 +14,7 @@ import {
|
|||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/AlertsSummaryWidget',
|
||||
title: 'app/AlertSummaryWidget',
|
||||
};
|
||||
|
||||
export const Compact = {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import {
|
||||
AlertsSummaryWidgetCompact,
|
||||
AlertsSummaryWidgetCompactProps,
|
||||
} from './alert_summary_widget_compact';
|
||||
import { render } from '@testing-library/react';
|
||||
import {
|
||||
mockedAlertSummaryResponse,
|
||||
mockedChartThemes,
|
||||
} from '../../../../../mock/alert_summary_widget';
|
||||
|
||||
describe('AlertsSummaryWidgetCompact', () => {
|
||||
const renderComponent = (props: Partial<AlertsSummaryWidgetCompactProps> = {}) =>
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AlertsSummaryWidgetCompact
|
||||
chartThemes={mockedChartThemes}
|
||||
onClick={jest.fn}
|
||||
{...mockedAlertSummaryResponse}
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
it('should render AlertsSummaryWidgetCompact', async () => {
|
||||
const alertSummaryWidget = renderComponent();
|
||||
|
||||
expect(alertSummaryWidget.queryByTestId('alertSummaryWidgetCompact')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render counts correctly', async () => {
|
||||
const alertSummaryWidget = renderComponent();
|
||||
|
||||
expect(alertSummaryWidget.queryByTestId('activeAlertsCount')).toHaveTextContent('2');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('22');
|
||||
});
|
||||
|
||||
it('should render higher counts correctly', async () => {
|
||||
const alertSummaryWidget = renderComponent({
|
||||
activeAlertCount: 2000,
|
||||
});
|
||||
|
||||
expect(alertSummaryWidget.queryByTestId('activeAlertsCount')).toHaveTextContent('2k');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('2.02k');
|
||||
});
|
||||
});
|
|
@ -6,18 +6,12 @@
|
|||
*/
|
||||
|
||||
import React, { MouseEvent } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, AlertStatus } from '@kbn/rule-data-utils';
|
||||
import { AlertStateInfo } from './alert_state_info';
|
||||
import { ACTIVE_ALERT_LABEL, ALL_ALERT_LABEL, RECOVERED_ALERT_LABEL } from './constants';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { Axis, Chart, CurveType, LineSeries, Position, ScaleType, Settings } from '@elastic/charts';
|
||||
import { EUI_SPARKLINE_THEME_PARTIAL } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { AlertStatus } from '@kbn/rule-data-utils';
|
||||
import { AlertCounts } from './alert_counts';
|
||||
import { ALL_ALERT_COLOR, WIDGET_TITLE } from './constants';
|
||||
import { Alert, ChartThemes } from '../types';
|
||||
|
||||
export interface AlertsSummaryWidgetCompactProps {
|
||||
|
@ -25,7 +19,6 @@ export interface AlertsSummaryWidgetCompactProps {
|
|||
activeAlerts: Alert[];
|
||||
chartThemes: ChartThemes;
|
||||
recoveredAlertCount: number;
|
||||
recoveredAlerts: Alert[];
|
||||
timeRangeTitle?: JSX.Element | string;
|
||||
onClick: (status?: AlertStatus) => void;
|
||||
}
|
||||
|
@ -33,19 +26,23 @@ export interface AlertsSummaryWidgetCompactProps {
|
|||
export const AlertsSummaryWidgetCompact = ({
|
||||
activeAlertCount,
|
||||
activeAlerts,
|
||||
chartThemes,
|
||||
chartThemes: { theme, baseTheme },
|
||||
recoveredAlertCount,
|
||||
recoveredAlerts,
|
||||
timeRangeTitle,
|
||||
onClick,
|
||||
}: AlertsSummaryWidgetCompactProps) => {
|
||||
const domain = {
|
||||
min: 0,
|
||||
max: Math.max(
|
||||
...activeAlerts.map((alert) => alert.doc_count),
|
||||
...recoveredAlerts.map((alert) => alert.doc_count)
|
||||
),
|
||||
};
|
||||
const chartTheme = [
|
||||
theme,
|
||||
EUI_SPARKLINE_THEME_PARTIAL,
|
||||
{
|
||||
chartMargins: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleClick = (
|
||||
event: MouseEvent<HTMLAnchorElement | HTMLDivElement>,
|
||||
|
@ -66,63 +63,48 @@ export const AlertsSummaryWidgetCompact = ({
|
|||
onClick={handleClick}
|
||||
>
|
||||
<EuiFlexGroup direction="column">
|
||||
{!!timeRangeTitle && (
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h5>{WIDGET_TITLE}</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued" data-test-subj="timeRangeTitle">
|
||||
{timeRangeTitle}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h5 data-test-subj="totalAlertsCount">
|
||||
{ALL_ALERT_LABEL} ({activeAlertCount + recoveredAlertCount})
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
{!!timeRangeTitle && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued" data-test-subj="timeRangeTitle">
|
||||
{timeRangeTitle}
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
<AlertCounts
|
||||
activeAlertCount={activeAlertCount}
|
||||
recoveredAlertCount={recoveredAlertCount}
|
||||
onActiveClick={handleClick}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup wrap>
|
||||
{/* Active */}
|
||||
<EuiFlexItem style={{ minWidth: '200px' }}>
|
||||
<EuiLink
|
||||
onClick={(event: React.MouseEvent<HTMLAnchorElement>) =>
|
||||
handleClick(event, ALERT_STATUS_ACTIVE)
|
||||
}
|
||||
data-test-subj="activeAlerts"
|
||||
>
|
||||
<AlertStateInfo
|
||||
chartThemes={chartThemes}
|
||||
count={activeAlertCount}
|
||||
<Chart size={{ height: 50 }}>
|
||||
<Settings theme={chartTheme} baseTheme={baseTheme} tooltip={{ type: 'none' }} />
|
||||
<Axis hide id={'activeAlertsAxis'} position={Position.Left} showGridLines={false} />
|
||||
<LineSeries
|
||||
id={'activeAlertsChart'}
|
||||
xScaleType={ScaleType.Time}
|
||||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
data={activeAlerts}
|
||||
dataTestSubj="activeAlerts"
|
||||
domain={domain}
|
||||
id="active"
|
||||
stroke="#E7664C"
|
||||
title={ACTIVE_ALERT_LABEL}
|
||||
lineSeriesStyle={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
stroke: ALL_ALERT_COLOR,
|
||||
},
|
||||
}}
|
||||
curve={CurveType.CURVE_MONOTONE_X}
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
{/* Recovered */}
|
||||
<EuiFlexItem style={{ minWidth: '200px' }}>
|
||||
<EuiLink
|
||||
onClick={(event: React.MouseEvent<HTMLAnchorElement>) =>
|
||||
handleClick(event, ALERT_STATUS_RECOVERED)
|
||||
}
|
||||
data-test-subj="recoveredAlerts"
|
||||
>
|
||||
<AlertStateInfo
|
||||
chartThemes={chartThemes}
|
||||
count={recoveredAlertCount}
|
||||
data={recoveredAlerts}
|
||||
dataTestSubj="recoveredAlerts"
|
||||
domain={domain}
|
||||
id="recovered"
|
||||
stroke="#54B399"
|
||||
title={RECOVERED_ALERT_LABEL}
|
||||
/>
|
||||
</EuiLink>
|
||||
</Chart>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
|
||||
export default {
|
||||
component: Component,
|
||||
title: 'app/AlertsSummaryWidget',
|
||||
title: 'app/AlertSummaryWidget',
|
||||
};
|
||||
|
||||
export const FullSize = {
|
||||
|
|
|
@ -7,20 +7,18 @@
|
|||
|
||||
import React from 'react';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import { AlertsSummaryWidgetFullSize } from './alert_summary_widget_full_size';
|
||||
import {
|
||||
AlertsSummaryWidgetFullSize,
|
||||
AlertsSummaryWidgetFullSizeProps,
|
||||
} from './alert_summary_widget_full_size';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AlertSummaryWidgetProps } from '..';
|
||||
import {
|
||||
mockedAlertSummaryResponse,
|
||||
mockedChartThemes,
|
||||
} from '../../../../../mock/alert_summary_widget';
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
||||
useUiSetting: jest.fn(() => false),
|
||||
}));
|
||||
|
||||
describe('AlertSummaryWidgetFullSize', () => {
|
||||
const renderComponent = (props: Partial<AlertSummaryWidgetProps> = {}) =>
|
||||
const renderComponent = (props: Partial<AlertsSummaryWidgetFullSizeProps> = {}) =>
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AlertsSummaryWidgetFullSize
|
||||
|
@ -41,7 +39,15 @@ describe('AlertSummaryWidgetFullSize', () => {
|
|||
const alertSummaryWidget = renderComponent();
|
||||
|
||||
expect(alertSummaryWidget.queryByTestId('activeAlertsCount')).toHaveTextContent('2');
|
||||
expect(alertSummaryWidget.queryByTestId('recoveredAlertsCount')).toHaveTextContent('15');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('17');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('22');
|
||||
});
|
||||
|
||||
it('should render higher counts correctly', async () => {
|
||||
const alertSummaryWidget = renderComponent({
|
||||
activeAlertCount: 2000,
|
||||
});
|
||||
|
||||
expect(alertSummaryWidget.queryByTestId('activeAlertsCount')).toHaveTextContent('2k');
|
||||
expect(alertSummaryWidget.queryByTestId('totalAlertsCount')).toHaveTextContent('2.02k');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,15 +8,9 @@
|
|||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { Axis, Chart, CurveType, LineSeries, Position, ScaleType, Settings } from '@elastic/charts';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, useEuiTheme } from '@elastic/eui';
|
||||
import {
|
||||
ACTIVE_ALERT_LABEL,
|
||||
ACTIVE_COLOR,
|
||||
ALL_ALERT_LABEL,
|
||||
RECOVERED_ALERT_LABEL,
|
||||
RECOVERED_COLOR,
|
||||
TOOLTIP_DATE_FORMAT,
|
||||
} from './constants';
|
||||
import { EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { AlertCounts } from './alert_counts';
|
||||
import { ALL_ALERT_COLOR, TOOLTIP_DATE_FORMAT } from './constants';
|
||||
import { Alert, ChartThemes } from '../types';
|
||||
|
||||
export interface AlertsSummaryWidgetFullSizeProps {
|
||||
|
@ -24,7 +18,6 @@ export interface AlertsSummaryWidgetFullSizeProps {
|
|||
activeAlerts: Alert[];
|
||||
chartThemes: ChartThemes;
|
||||
recoveredAlertCount: number;
|
||||
recoveredAlerts: Alert[];
|
||||
dateFormat?: string;
|
||||
}
|
||||
|
||||
|
@ -34,9 +27,7 @@ export const AlertsSummaryWidgetFullSize = ({
|
|||
chartThemes: { theme, baseTheme },
|
||||
dateFormat,
|
||||
recoveredAlertCount,
|
||||
recoveredAlerts,
|
||||
}: AlertsSummaryWidgetFullSizeProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const chartTheme = [
|
||||
theme,
|
||||
{
|
||||
|
@ -53,48 +44,15 @@ export const AlertsSummaryWidgetFullSize = ({
|
|||
hasShadow={false}
|
||||
paddingSize="none"
|
||||
>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="xl" alignItems="flexStart" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiText color={euiTheme.colors.text}>
|
||||
<h3 data-test-subj="totalAlertsCount">
|
||||
{activeAlertCount + recoveredAlertCount}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{ALL_ALERT_LABEL}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color={euiTheme.colors.dangerText}>
|
||||
<h3 data-test-subj="activeAlertsCount">{activeAlertCount}</h3>
|
||||
</EuiText>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{ACTIVE_ALERT_LABEL}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color={euiTheme.colors.successText}>
|
||||
<h3 data-test-subj="recoveredAlertsCount">{recoveredAlertCount}</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiText size="xs" color="subdued">
|
||||
{RECOVERED_ALERT_LABEL}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<AlertCounts
|
||||
activeAlertCount={activeAlertCount}
|
||||
recoveredAlertCount={recoveredAlertCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size="l" />
|
||||
<Chart size={['100%', 170]}>
|
||||
<Settings
|
||||
showLegend
|
||||
legendPosition={Position.Right}
|
||||
theme={chartTheme}
|
||||
baseTheme={baseTheme}
|
||||
|
@ -121,33 +79,15 @@ export const AlertsSummaryWidgetFullSize = ({
|
|||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
color={[ALL_ALERT_COLOR]}
|
||||
data={activeAlerts}
|
||||
color={[ACTIVE_COLOR]}
|
||||
lineSeriesStyle={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
},
|
||||
point: { visible: true, radius: 3, strokeWidth: 2 },
|
||||
point: { visible: false },
|
||||
}}
|
||||
curve={CurveType.CURVE_MONOTONE_X}
|
||||
timeZone="UTC"
|
||||
/>
|
||||
<LineSeries
|
||||
id="Recovered"
|
||||
xScaleType={ScaleType.Time}
|
||||
yScaleType={ScaleType.Linear}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
data={recoveredAlerts}
|
||||
color={[RECOVERED_COLOR]}
|
||||
lineSeriesStyle={{
|
||||
line: {
|
||||
strokeWidth: 2,
|
||||
},
|
||||
point: { visible: true, radius: 3, strokeWidth: 2 },
|
||||
}}
|
||||
curve={CurveType.CURVE_MONOTONE_X}
|
||||
timeZone="UTC"
|
||||
/>
|
||||
</Chart>
|
||||
</EuiPanel>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { ALERT_COUNT_FORMAT, ALERTS_LABEL, ALL_ALERT_COLOR } from './constants';
|
||||
|
||||
interface Props {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const AllAlertCounts = ({ count }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<EuiText color={ALL_ALERT_COLOR}>
|
||||
<h3 data-test-subj="totalAlertsCount">{numeral(count).format(ALERT_COUNT_FORMAT)}</h3>
|
||||
</EuiText>
|
||||
<EuiText size="s" color="subdued">
|
||||
{ALERTS_LABEL}
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -5,30 +5,33 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
export const ACTIVE_COLOR = '#E7664C';
|
||||
export const RECOVERED_COLOR = '#54B399';
|
||||
export const TOOLTIP_DATE_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||
export const ALERT_COUNT_FORMAT = '0.[00]a';
|
||||
|
||||
export const ALL_ALERT_LABEL = (
|
||||
const visColors = euiPaletteColorBlind();
|
||||
export const ALL_ALERT_COLOR = visColors[1];
|
||||
|
||||
export const WIDGET_TITLE = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title"
|
||||
defaultMessage="Alert activity"
|
||||
/>
|
||||
);
|
||||
|
||||
export const ALERTS_LABEL = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.alerts"
|
||||
defaultMessage="Alerts"
|
||||
/>
|
||||
);
|
||||
|
||||
export const ACTIVE_ALERT_LABEL = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeLabel"
|
||||
defaultMessage="Active"
|
||||
/>
|
||||
);
|
||||
|
||||
export const RECOVERED_ALERT_LABEL = (
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.ruleDetails.rule.ruleSummary.recoveredLabel"
|
||||
defaultMessage="Recovered"
|
||||
id="xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.activeNow"
|
||||
defaultMessage="Active now"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
const COMPACT_COMPONENT_SELECTOR = 'alertSummaryWidgetCompact';
|
||||
const COMPACT_TIME_RANGE_TITLE_SELECTOR = 'timeRangeTitle';
|
||||
const COMPACT_ACTIVE_ALERTS_SELECTOR = 'activeAlerts';
|
||||
const COMPACT_RECOVERED_ALERTS_SELECTOR = 'recoveredAlerts';
|
||||
|
||||
export function ObservabilityAlertSummaryWidgetProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
@ -27,14 +26,14 @@ export function ObservabilityAlertSummaryWidgetProvider({ getService }: FtrProvi
|
|||
return await testSubjects.find(COMPACT_ACTIVE_ALERTS_SELECTOR);
|
||||
};
|
||||
|
||||
const getCompactRecoveredAlertSelector = async () => {
|
||||
return await testSubjects.find(COMPACT_RECOVERED_ALERTS_SELECTOR);
|
||||
const getCompactWidgetSelector = async () => {
|
||||
return await testSubjects.find(COMPACT_COMPONENT_SELECTOR);
|
||||
};
|
||||
|
||||
return {
|
||||
getCompactActiveAlertSelector,
|
||||
getCompactComponentSelectorOrFail,
|
||||
getCompactTimeRangeTitle,
|
||||
getCompactActiveAlertSelector,
|
||||
getCompactRecoveredAlertSelector,
|
||||
getCompactWidgetSelector,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -169,16 +169,16 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(url.includes(to.replaceAll(':', '%3A'))).to.be(true);
|
||||
});
|
||||
|
||||
it('handles clicking on recovered correctly', async () => {
|
||||
const recoveredAlerts =
|
||||
await observability.components.alertSummaryWidget.getCompactRecoveredAlertSelector();
|
||||
await recoveredAlerts.click();
|
||||
it('handles clicking on widget correctly', async () => {
|
||||
const compactWidget =
|
||||
await observability.components.alertSummaryWidget.getCompactWidgetSelector();
|
||||
await compactWidget.click();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
const { from, to } = await observability.components.alertSearchBar.getAbsoluteTimeRange();
|
||||
|
||||
expect(url.includes('tabId=alerts')).to.be(true);
|
||||
expect(url.includes('status%3Arecovered')).to.be(true);
|
||||
expect(url.includes('status%3Aall')).to.be(true);
|
||||
expect(url.includes(from.replaceAll(':', '%3A'))).to.be(true);
|
||||
expect(url.includes(to.replaceAll(':', '%3A'))).to.be(true);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue