Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AO] Allow providing custom time range for Alert Summary Widget #147253

Merged
merged 15 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 moment from 'moment';
import { getDefaultAlertSummaryTimeRange } from '.';

describe('getDefaultAlertSummaryTimeRange', () => {
it('should return default time in UTC format', () => {
const defaultTimeRange = getDefaultAlertSummaryTimeRange();
const utcFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ';

expect(moment(defaultTimeRange.utcFrom, utcFormat, true).isValid()).toBeTruthy();
expect(moment(defaultTimeRange.utcTo, utcFormat, true).isValid()).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -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 { getAbsoluteTimeRange } from '@kbn/data-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';

export const getDefaultAlertSummaryTimeRange = () => {
const { to, from } = getAbsoluteTimeRange({
from: 'now-30d',
to: 'now',
});

return {
utcFrom: from,
utcTo: to,
title: (
<FormattedMessage
id="xpack.observability.alertsSummaryWidget.last30days"
defaultMessage="Last 30 days"
/>
),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { getDefaultAlertSummaryTimeRange } from './get_alert_summary_time_range';
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { fromQuery, toQuery } from '../../utils/url';
import { ObservabilityAlertSearchbarWithUrlSync } from '../../components/shared/alert_search_bar';
import { DeleteModalConfirmation } from './components/delete_modal_confirmation';
import { CenterJustifiedSpinner } from './components/center_justified_spinner';
import { getDefaultAlertSummaryTimeRange } from './helpers';

import {
EXECUTION_TAB,
ALERTS_TAB,
Expand Down Expand Up @@ -108,6 +110,7 @@ export function RuleDetailsPage() {
const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false);
const [isRuleEditPopoverOpen, setIsRuleEditPopoverOpen] = useState(false);
const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>();
const [defaultAlertTimeRange] = useState(getDefaultAlertSummaryTimeRange);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use a state for this? Can it be just a variable in this scope, or a constant in the module?

Copy link
Member Author

@maryam-saeidi maryam-saeidi Dec 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for having this state is to run getAbsoluteTimeRange only once during the rendering of this component. getAbsoluteTimeRange will return the absolute time for the last 30 days at the time of the execution and I didn't want this value to be created more than once in this component but I also didn't want to have it created when the module is loaded so I used the state in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - I also saw that people use useRef to do that to avoid the state

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I'll keep it in the state to be able to refresh it in case we want to.

const ruleQuery = useRef([
{ query: `kibana.alert.rule.uuid: ${ruleId}`, language: 'kuery' },
] as Query[]);
Expand Down Expand Up @@ -379,6 +382,7 @@ export function RuleDetailsPage() {
rule={rule}
filteredRuleTypes={filteredRuleTypes}
onClick={(status) => onAlertSummaryWidgetClick(status)}
timeRange={defaultAlertTimeRange}
/>
</EuiFlexItem>
<EuiSpacer size="m" />
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -35426,7 +35426,6 @@
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "Tous",
"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.last30days": "30 derniers jours",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "Alertes",
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "Supprimer la règle",
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "Désactiver",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -35398,7 +35398,6 @@
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "すべて",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingBody": "アラート概要の読み込みエラーが発生しました。ヘルプについては、管理者にお問い合わせください。",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingTitle": "アラート概要を読み込めません",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.last30days": "過去30日間",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "アラート",
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "ルールの削除",
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "無効にする",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -35434,7 +35434,6 @@
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.allAlertsLabel": "全部",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingBody": "加载告警摘要时出现错误。请联系您的管理员寻求帮助。",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.errorLoadingTitle": "无法加载告警摘要",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.last30days": "过去 30 天",
"xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.title": "告警",
"xpack.triggersActionsUI.sections.ruleDetails.deleteRuleButtonLabel": "删除规则",
"xpack.triggersActionsUI.sections.ruleDetails.disableRuleButtonLabel": "禁用",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
import { renderHook } from '@testing-library/react-hooks';
import { useKibana } from '../../common/lib/kibana';
import { mockAggsResponse } from '../mock/rule_details/alert_summary';
import { mockAggsResponse, mockAlertSummaryTimeRange } from '../mock/alert_summary_widget';
import { useLoadRuleAlertsAggs } from './use_load_rule_alerts_aggregations';

jest.mock('../../common/lib/kibana');
Expand All @@ -26,6 +26,7 @@ describe('useLoadRuleAlertsAggs', () => {
useLoadRuleAlertsAggs({
features: ALERTS_FEATURE_ID,
ruleId: 'c95bc120-1d56-11ed-9cc7-e7214ada1128',
timeRange: mockAlertSummaryTimeRange,
})
);
expect(result.current).toEqual({
Expand All @@ -44,15 +45,17 @@ describe('useLoadRuleAlertsAggs', () => {

it('should have the correct query body sent to Elasticsearch', async () => {
const ruleId = 'c95bc120-1d56-11ed-9cc7-e7214ada1128';
const { utcFrom, utcTo } = mockAlertSummaryTimeRange;
const { waitForNextUpdate } = renderHook(() =>
useLoadRuleAlertsAggs({
features: ALERTS_FEATURE_ID,
ruleId,
timeRange: mockAlertSummaryTimeRange,
})
);

await waitForNextUpdate();
const body = `{"index":"mock_index","size":0,"query":{"bool":{"must":[{"term":{"kibana.alert.rule.uuid":"${ruleId}"}},{"range":{"@timestamp":{"gte":"now-30d","lt":"now"}}},{"bool":{"should":[{"term":{"kibana.alert.status":"active"}},{"term":{"kibana.alert.status":"recovered"}}]}}]}},"aggs":{"total":{"filters":{"filters":{"totalActiveAlerts":{"term":{"kibana.alert.status":"active"}},"totalRecoveredAlerts":{"term":{"kibana.alert.status":"recovered"}}}}}}}`;
const body = `{"index":"mock_index","size":0,"query":{"bool":{"must":[{"term":{"kibana.alert.rule.uuid":"${ruleId}"}},{"range":{"@timestamp":{"gte":"${utcFrom}","lt":"${utcTo}"}}},{"bool":{"should":[{"term":{"kibana.alert.status":"active"}},{"term":{"kibana.alert.status":"recovered"}}]}}]}},"aggs":{"total":{"filters":{"filters":{"totalActiveAlerts":{"term":{"kibana.alert.status":"active"}},"totalRecoveredAlerts":{"term":{"kibana.alert.status":"recovered"}}}}}}}`;

expect(useKibanaMock().services.http.post).toHaveBeenCalledWith(
'/internal/rac/alerts/find',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ import { HttpSetup } from '@kbn/core/public';
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants';
import { useKibana } from '../../common/lib/kibana';

export interface AlertSummaryTimeRange {
utcFrom: string;
utcTo: string;
title: JSX.Element | string;
}

interface UseLoadRuleAlertsAggs {
features: string;
ruleId: string;
timeRange: AlertSummaryTimeRange;
}
interface RuleAlertsAggs {
active: number;
Expand All @@ -29,11 +36,12 @@ interface LoadRuleAlertsAggs {
};
errorRuleAlertsAggs?: string;
}

interface IndexName {
index: string;
}

export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAggs) {
export function useLoadRuleAlertsAggs({ features, ruleId, timeRange }: UseLoadRuleAlertsAggs) {
const { http } = useKibana().services;
const [ruleAlertsAggs, setRuleAlertsAggs] = useState<LoadRuleAlertsAggs>({
isLoadingRuleAlertsAggs: true,
Expand All @@ -56,6 +64,7 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg
index,
ruleId,
signal: abortCtrlRef.current.signal,
timeRange,
});
if (error) throw error;
if (!isCancelledRef.current) {
Expand All @@ -79,7 +88,7 @@ export function useLoadRuleAlertsAggs({ features, ruleId }: UseLoadRuleAlertsAgg
}
}
}
}, [http, features, ruleId]);
}, [features, http, ruleId, timeRange]);
useEffect(() => {
loadRuleAlertsAgg();
}, [loadRuleAlertsAgg]);
Expand Down Expand Up @@ -107,11 +116,13 @@ async function fetchRuleAlertsAggByTimeRange({
index,
ruleId,
signal,
timeRange: { utcFrom, utcTo },
}: {
http: HttpSetup;
index: string;
ruleId: string;
signal: AbortSignal;
timeRange: AlertSummaryTimeRange;
}): Promise<RuleAlertsAggs> {
try {
const res = await http.post<AsApiContract<any>>(`${BASE_RAC_ALERTS_API_PATH}/find`, {
Expand All @@ -130,8 +141,8 @@ async function fetchRuleAlertsAggByTimeRange({
{
range: {
'@timestamp': {
gte: 'now-30d',
lt: 'now',
gte: utcFrom,
lt: utcTo,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/

import { Rule } from '../../../../types';
import { AlertSummaryTimeRange } from '../../hooks/use_load_rule_alerts_aggregations';
import { Rule } from '../../../types';

export const mockRule = (): Rule => {
return {
Expand Down Expand Up @@ -77,3 +78,9 @@ export const mockAggsResponse = () => {
},
};
};

export const mockAlertSummaryTimeRange: AlertSummaryTimeRange = {
utcFrom: 'mockedUtcFrom',
utcTo: 'mockedUtcTo',
title: 'mockedTitle',
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const Overview = {
args: {
active: 15,
recovered: 53,
timeRange: 'Last 30 days',
timeRangeTitle: 'Last 30 days',
onClick: action('clicked'),
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { AlertsSummaryWidgetUIProps } from './types';
export const AlertsSummaryWidgetUI = ({
active,
recovered,
timeRange,
timeRangeTitle,
onClick,
}: AlertsSummaryWidgetUIProps) => {
const handleClick = (
Expand Down Expand Up @@ -56,11 +56,11 @@ export const AlertsSummaryWidgetUI = ({
/>
</h5>
</EuiTitle>
{!!timeRange && (
{!!timeRangeTitle && (
<>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
{timeRange}
<EuiText size="s" color="subdued" data-test-subj="timeRangeTitle">
{timeRangeTitle}
</EuiText>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import { AlertStatus } from '@kbn/rule-data-utils';
export interface AlertsSummaryWidgetUIProps {
active: number;
recovered: number;
timeRange: JSX.Element | string;
timeRangeTitle: JSX.Element | string;
onClick: (status?: AlertStatus) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { RuleAlertsSummary } from './rule_alerts_summary';
import { mount, ReactWrapper } from 'enzyme';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
import { mockRule } from '../../../../mock/rule_details/alert_summary';
import { mockAlertSummaryTimeRange, mockRule } from '../../../../mock/alert_summary_widget';

jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
useUiSetting: jest.fn().mockImplementation(() => true),
Expand Down Expand Up @@ -48,6 +48,10 @@ const ruleTypes = [

describe('Rule Alert Summary', () => {
let wrapper: ReactWrapper;
const mockedTimeRange = {
...mockAlertSummaryTimeRange,
title: <h3 data-test-subj="mockedTimeRangeTitle">mockedTimeRangeTitle</h3>,
};

async function setup() {
const mockedRule = mockRule();
Expand All @@ -60,6 +64,7 @@ describe('Rule Alert Summary', () => {
rule={mockedRule}
filteredRuleTypes={['apm', 'uptime', 'metric', 'logs']}
onClick={jest.fn}
timeRange={mockedTimeRange}
/>
</IntlProvider>
);
Expand All @@ -77,5 +82,8 @@ describe('Rule Alert Summary', () => {
expect(wrapper.find('[data-test-subj="activeAlertsCount"]').text()).toEqual('1');
expect(wrapper.find('[data-test-subj="recoveredAlertsCount"]').text()).toBe('7');
expect(wrapper.find('[data-test-subj="totalAlertsCount"]').text()).toBe('8');
expect(wrapper.find('[data-test-subj="mockedTimeRangeTitle"]').text()).toBe(
'mockedTimeRangeTitle'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

import { EuiLoadingSpinner } from '@elastic/eui';
import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useEffect, useState } from 'react';
import { useLoadRuleAlertsAggs } from '../../../../hooks/use_load_rule_alerts_aggregations';
import { useLoadRuleTypes } from '../../../../hooks/use_load_rule_types';
import { RuleAlertsSummaryProps } from '.';
import { AlertSummaryWidgetError, AlertsSummaryWidgetUI } from './components';

export const RuleAlertsSummary = ({ rule, filteredRuleTypes, onClick }: RuleAlertsSummaryProps) => {
export const RuleAlertsSummary = ({
rule,
filteredRuleTypes,
onClick,
timeRange,
}: RuleAlertsSummaryProps) => {
const [features, setFeatures] = useState<string>('');
const { ruleTypes } = useLoadRuleTypes({
filteredRuleTypes,
Expand All @@ -26,6 +30,7 @@ export const RuleAlertsSummary = ({ rule, filteredRuleTypes, onClick }: RuleAler
} = useLoadRuleAlertsAggs({
ruleId: rule.id,
features,
timeRange,
});

useEffect(() => {
Expand All @@ -42,12 +47,7 @@ export const RuleAlertsSummary = ({ rule, filteredRuleTypes, onClick }: RuleAler
active={active}
onClick={onClick}
recovered={recovered}
timeRange={
<FormattedMessage
id="xpack.triggersActionsUI.sections.ruleDetails.alertsSummary.last30days"
defaultMessage="Last 30 days"
/>
}
timeRangeTitle={timeRange.title}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*/

import { AlertStatus } from '@kbn/rule-data-utils';
import { AlertSummaryTimeRange } from '../../../../hooks/use_load_rule_alerts_aggregations';
import { Rule } from '../../../../../types';

export interface RuleAlertsSummaryProps {
rule: Rule;
filteredRuleTypes: string[];
onClick: (status?: AlertStatus) => void;
timeRange: AlertSummaryTimeRange;
}