Skip to content

Commit

Permalink
Displaying alert data in alert summary in alert details page (#140339)
Browse files Browse the repository at this point in the history
* displaying data in alert summary in alert details page

* refactoring index to include only exports

* fixing path error

* removing tooltip as not required

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fixing failing tests

* Added unit tests for alert summary, refactoring

* fix: hook was called conditionally

* fixed path errors

* removing dependency on ruleId as not required

* removing dependency on ruleId as not required

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fixing route

* removing ruleid dependency

* fixing error

* minor changes

* removing ruleId dependency as not required

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* changes as per design, bug fix

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* minor changes

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* changes as per feedback

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* making alertId not optional

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* removing hardcoded field names

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fixing type error

* fixing CI errors

* fixing tests

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
benakansara and kibanamachine authored Sep 23, 2022
1 parent 91edd77 commit 1b496c5
Show file tree
Hide file tree
Showing 23 changed files with 495 additions and 247 deletions.
5 changes: 1 addition & 4 deletions x-pack/plugins/observability/public/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ export const paths = {
rules: RULES_PAGE_LINK,
ruleDetails: (ruleId?: string | null) =>
ruleId ? `${RULES_PAGE_LINK}/${encodeURI(ruleId)}` : RULES_PAGE_LINK,
alertDetails: (alertId?: string | null, ruleId?: string | null) =>
alertId && ruleId
? `${ALERT_PAGE_LINK}/rules/${encodeURI(ruleId)}/alerts/${encodeURI(alertId)}`
: ALERT_PAGE_LINK,
alertDetails: (alertId: string) => `${ALERT_PAGE_LINK}/${encodeURI(alertId)}`,
},
management: {
rules: '/app/management/insightsAndAlerting/triggersActions/rules',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { useState, useMemo, useEffect } from 'react';

import { HttpSetup } from '@kbn/core/public';
import { useKibana } from '../../utils/kibana_react';
import { useKibana } from '../utils/kibana_react';

type DataFetcher<T, R> = (params: T, ctrl: AbortController, http: HttpSetup) => Promise<R>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
*/

import { act, renderHook } from '@testing-library/react-hooks';
import { kibanaStartMock } from '../../utils/kibana_react.mock';
import { kibanaStartMock } from '../utils/kibana_react.mock';
import { useFetchAlertData } from './use_fetch_alert_data';

const mockUseKibanaReturnValue = kibanaStartMock.startContract();

jest.mock('../../utils/kibana_react', () => ({
jest.mock('../utils/kibana_react', () => ({
__esModule: true,
useKibana: jest.fn(() => mockUseKibanaReturnValue),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
*/

import { act, renderHook } from '@testing-library/react-hooks';
import { kibanaStartMock } from '../../utils/kibana_react.mock';
import { TopAlert } from '../alerts';
import * as pluginContext from '../../hooks/use_plugin_context';
import { createObservabilityRuleTypeRegistryMock } from '../..';
import { PluginContextValue } from '../../context/plugin_context';
import { kibanaStartMock } from '../utils/kibana_react.mock';
import { TopAlert } from '../pages/alerts';
import * as pluginContext from './use_plugin_context';
import { createObservabilityRuleTypeRegistryMock } from '..';
import { PluginContextValue } from '../context/plugin_context';
import { useFetchAlertDetail } from './use_fetch_alert_detail';

const mockUseKibanaReturnValue = kibanaStartMock.startContract();

jest.mock('../../utils/kibana_react', () => ({
jest.mock('../utils/kibana_react', () => ({
__esModule: true,
useKibana: jest.fn(() => mockUseKibanaReturnValue),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { isEmpty } from 'lodash';

import { HttpSetup } from '@kbn/core/public';
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { TopAlert, parseAlert } from '../alerts';
import { ObservabilityRuleTypeRegistry } from '../..';
import { usePluginContext } from './use_plugin_context';
import { TopAlert, parseAlert } from '../pages/alerts';
import { ObservabilityRuleTypeRegistry } from '..';
import { useDataFetcher } from './use_data_fetcher';

interface AlertDetailParams {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
import { render } from '../../../utils/test_helper';
import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail';
import { AlertDetails } from './alert_details';
import { Chance } from 'chance';
import { useParams } from 'react-router-dom';
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
import { ConfigSchema } from '../../../plugin';
import { alert, alertWithNoData } from '../mock/alert';
import { waitFor } from '@testing-library/react';

jest.mock('../../../hooks/use_fetch_alert_detail');
jest.mock('../../../hooks/use_breadcrumbs');
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn(),
}));

const useFetchAlertDetailMock = useFetchAlertDetail as jest.Mock;
const useParamsMock = useParams as jest.Mock;
const useBreadcrumbsMock = useBreadcrumbs as jest.Mock;

const chance = new Chance();

const params = {
alertId: chance.guid(),
};

const config = {
unsafe: {
alertDetails: { enabled: true },
},
} as ConfigSchema;

describe('Alert details', () => {
jest
.spyOn(useUiSettingHook, 'useUiSetting')
.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');

beforeEach(() => {
jest.clearAllMocks();
useParamsMock.mockReturnValue(params);
useBreadcrumbsMock.mockReturnValue([]);
});

it('should show alert summary', async () => {
useFetchAlertDetailMock.mockReturnValue([false, alert]);

const alertDetails = render(<AlertDetails />, config);

expect(alertDetails.queryByTestId('alertDetails')).toBeTruthy();
await waitFor(() => expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy());
expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy();
});

it('should show error loading the alert details', async () => {
useFetchAlertDetailMock.mockReturnValue([false, alertWithNoData]);

const alertDetails = render(<AlertDetails />, config);

expect(alertDetails.queryByTestId('alertDetailsError')).toBeTruthy();
expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy();
expect(alertDetails.queryByTestId('alertDetails')).toBeFalsy();
});

it('should show loading spinner', async () => {
useFetchAlertDetailMock.mockReturnValue([true, alertWithNoData]);

const alertDetails = render(<AlertDetails />, config);

expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeTruthy();
expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy();
expect(alertDetails.queryByTestId('alertDetails')).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { useParams } from 'react-router-dom';
import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
import { useKibana } from '../../../utils/kibana_react';
import { ObservabilityAppServices } from '../../../application/types';
import { usePluginContext } from '../../../hooks/use_plugin_context';
import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs';
import { paths } from '../../../config/paths';
import { AlertDetailsPathParams } from '../types';
import { CenterJustifiedSpinner } from '../../rule_details/components/center_justified_spinner';
import { AlertSummary } from '.';
import PageNotFound from '../../404';
import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail';

export function AlertDetails() {
const { http } = useKibana<ObservabilityAppServices>().services;
const { ObservabilityPageTemplate, config } = usePluginContext();
const { alertId } = useParams<AlertDetailsPathParams>();
const [isLoading, alert] = useFetchAlertDetail(alertId);

useBreadcrumbs([
{
href: http.basePath.prepend(paths.observability.alerts),
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
},
]);

// Redirect to the the 404 page when the user hit the page url directly in the browser while the feature flag is off.
if (!config.unsafe.alertDetails.enabled) {
return <PageNotFound />;
}

if (isLoading) {
return <CenterJustifiedSpinner />;
}

if (!isLoading && !alert)
return (
<EuiPanel data-test-subj="alertDetailsError">
<EuiEmptyPrompt
iconType="alert"
color="danger"
title={
<h2>
{i18n.translate('xpack.observability.alertDetails.errorPromptTitle', {
defaultMessage: 'Unable to load alert details',
})}
</h2>
}
body={
<p>
{i18n.translate('xpack.observability.alertDetails.errorPromptBody', {
defaultMessage: 'There was an error loading the alert details.',
})}
</p>
}
/>
</EuiPanel>
);

return (
<ObservabilityPageTemplate data-test-subj="alertDetails">
<AlertSummary alert={alert} />
</ObservabilityPageTemplate>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
import { render } from '../../../utils/test_helper';
import { AlertSummary } from './alert_summary';
import { asDuration } from '../../../../common/utils/formatters';
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
import { useKibana } from '../../../utils/kibana_react';
import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks';
import { waitFor } from '@testing-library/react';
import { alertWithTags, alertWithNoData, tags } from '../mock/alert';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn(),
}));

jest.mock('../../../utils/kibana_react');

const useKibanaMock = useKibana as jest.Mock;

const mockKibana = () => {
useKibanaMock.mockReturnValue({
services: {
...kibanaStartMock.startContract(),
triggersActionsUi: triggersActionsUiMock.createStart(),
},
});
};

describe('Alert summary', () => {
jest
.spyOn(useUiSettingHook, 'useUiSetting')
.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');

beforeEach(() => {
jest.clearAllMocks();
mockKibana();
});

it('should show alert data', async () => {
const alertSummary = render(<AlertSummary alert={alertWithTags} />);

expect(alertSummary.queryByText('1957')).toBeInTheDocument();
expect(alertSummary.queryByText(asDuration(882076000))).toBeInTheDocument();
expect(alertSummary.queryByText('Active')).toBeInTheDocument();
expect(alertSummary.queryByText('Sep 2, 2021 @ 08:54:09.674')).toBeInTheDocument();
expect(
alertSummary.getByText('Sep 2, 2021 @ 09:08:51.750', { exact: false })
).toBeInTheDocument();
await waitFor(() => expect(alertSummary.queryByTestId('tagsOutPopover')).toBeInTheDocument());
expect(alertSummary.queryByText(tags[0])).toBeInTheDocument();
});

it('should show empty "-" for fields when no data available', async () => {
const alertSummary = render(<AlertSummary alert={alertWithNoData} />);

expect(alertSummary.queryByTestId('noAlertStatus')).toBeInTheDocument();
expect(alertSummary.queryByTestId('noAlertStatus')).toHaveTextContent('-');
await waitFor(() =>
expect(alertSummary.queryByTestId('tagsOutPopover')).not.toBeInTheDocument()
);
});
});
Loading

0 comments on commit 1b496c5

Please sign in to comment.