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

[Actionable Observability] Add Alert Details page header #140299

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const CaseViewAlerts = ({ caseData }: CaseViewAlertsProps) => {
}),
[caseData.comments]
);

const alertRegistrationContexts = useMemo(
() => getRegistrationContextFromAlerts(caseData.comments),
[caseData.comments]
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/cases/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ const uiMock: jest.Mocked<CasesUiStart['ui']> = {
getRecentCases: jest.fn(),
};

export const openAddToExistingCaseModalMock = jest.fn();

const hooksMock: jest.Mocked<CasesUiStart['hooks']> = {
getUseCasesAddToNewCaseFlyout: jest.fn(),
getUseCasesAddToExistingCaseModal: jest.fn(),
getUseCasesAddToExistingCaseModal: jest.fn().mockImplementation(() => ({
open: openAddToExistingCaseModalMock,
})),
};

const helpersMock: jest.Mocked<CasesUiStart['helpers']> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ interface AlertDetailParams {

export const useFetchAlertDetail = (id: string): [boolean, TopAlert | null] => {
const { observabilityRuleTypeRegistry } = usePluginContext();

const params = useMemo(
() => ({ id, ruleType: observabilityRuleTypeRegistry }),
[id, observabilityRuleTypeRegistry]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,58 @@

import React from 'react';
import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
import { useParams } from 'react-router-dom';
import { Chance } from 'chance';
import { waitFor } from '@testing-library/react';
import { casesPluginMock } from '@kbn/cases-plugin/public/mocks';
import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks';

import { render } from '../../../utils/test_helper';
import { useKibana } from '../../../utils/kibana_react';
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
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 { AlertDetails } from './alert_details';
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(),
}));

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

const useKibanaMock = useKibana as jest.Mock;

const mockKibana = () => {
useKibanaMock.mockReturnValue({
services: {
...kibanaStartMock.startContract(),
cases: casesPluginMock.createStartContract(),
http: {
basePath: {
prepend: jest.fn(),
},
},
triggersActionsUi: triggersActionsUiMock.createStart(),
},
});
};

jest.mock('../../../hooks/use_fetch_alert_detail');
jest.mock('../../../hooks/use_breadcrumbs');
jest.mock('../../../hooks/use_get_user_cases_permissions', () => ({
useGetUserCasesPermissions: () => ({
all: true,
create: true,
delete: true,
push: true,
read: true,
update: true,
}),
}));

const useFetchAlertDetailMock = useFetchAlertDetail as jest.Mock;
const useParamsMock = useParams as jest.Mock;
const useBreadcrumbsMock = useBreadcrumbs as jest.Mock;
Expand All @@ -49,16 +84,20 @@ describe('Alert details', () => {
jest.clearAllMocks();
useParamsMock.mockReturnValue(params);
useBreadcrumbsMock.mockReturnValue([]);
mockKibana();
});

it('should show alert summary', async () => {
it('should show the alert detail page with all necessary components', 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('alertDetails')).toBeTruthy();
expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy();
expect(alertDetails.queryByTestId('page-title-container')).toBeTruthy();
expect(alertDetails.queryByTestId('alert-summary-container')).toBeTruthy();
});

it('should show error loading the alert details', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@ 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 { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail';

import { AlertSummary, HeaderActions, PageTitle } from '.';
import { CenterJustifiedSpinner } from '../../rule_details/components/center_justified_spinner';
import { AlertSummary } from '.';
import PageNotFound from '../../404';
import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail';

import { ObservabilityAppServices } from '../../../application/types';
import { AlertDetailsPathParams } from '../types';
import { observabilityFeatureId } from '../../../../common';
import { paths } from '../../../config/paths';

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

const CasesContext = getCasesContext();
const userCasesPermissions = canUseCases();

useBreadcrumbs([
{
href: http.basePath.prepend(paths.observability.alerts),
Expand Down Expand Up @@ -69,7 +82,22 @@ export function AlertDetails() {
);

return (
<ObservabilityPageTemplate data-test-subj="alertDetails">
<ObservabilityPageTemplate
pageHeader={{
pageTitle: <PageTitle title={alert?.reason} active={Boolean(alert?.active)} />,
rightSideItems: [
<CasesContext
owner={[observabilityFeatureId]}
permissions={userCasesPermissions}
features={{ alerts: { sync: false } }}
>
<HeaderActions alert={alert} />
</CasesContext>,
],
bottomBorder: false,
}}
data-test-subj="alertDetails"
>
<AlertSummary alert={alert} />
</ObservabilityPageTemplate>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function AlertSummary({ alert }: AlertSummaryProps) {
const tags = alert?.fields[ALERT_RULE_TAGS];

return (
<>
<div data-test-subj="alert-summary-container">
CoenWarmer marked this conversation as resolved.
Show resolved Hide resolved
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xxs">
Expand Down Expand Up @@ -161,6 +161,6 @@ export function AlertSummary({ alert }: AlertSummaryProps) {
</div>
</EuiFlexItem>
</EuiFlexGroup>
</>
</div>
);
}
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 { fireEvent } from '@testing-library/react';
import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks';
import { casesPluginMock, openAddToExistingCaseModalMock } from '@kbn/cases-plugin/public/mocks';

import { render } from '../../../utils/test_helper';
import { useKibana } from '../../../utils/kibana_react';
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
import { alertWithTags, mockAlertUuid } from '../mock/alert';

import { HeaderActions } from './header_actions';

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

const useKibanaMock = useKibana as jest.Mock;

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

const ruleId = '123';
const ruleName = '456';

jest.mock('../../../hooks/use_fetch_rule', () => {
return {
useFetchRule: () => ({
reloadRule: jest.fn(),
rule: {
id: ruleId,
name: ruleName,
},
}),
};
});

describe('Header Actions', () => {
beforeEach(() => {
jest.clearAllMocks();
mockKibana();
});

it('should display an actions button', () => {
const { queryByTestId } = render(<HeaderActions alert={alertWithTags} />);
expect(queryByTestId('alert-details-header-actions-menu-button')).toBeTruthy();
});

describe('when clicking the actions button', () => {
it('should offer an "add to case" button which opens the add to case modal', async () => {
const { getByTestId, findByRole } = render(<HeaderActions alert={alertWithTags} />);

fireEvent.click(await findByRole('button', { name: 'Actions' }));

fireEvent.click(getByTestId('add-to-case-button'));

expect(openAddToExistingCaseModalMock).toBeCalledWith({
attachments: [
{
alertId: mockAlertUuid,
index: '.internal.alerts-observability.metrics.alerts-*',
rule: {
id: ruleId,
name: ruleName,
},
type: 'alert',
},
],
});
});
});
});
Loading