[Actionable Observability] [ResponseOps] Remove view rule details link from the alerts table in the rule details page (#137576)

* Remove view rule details link in rule details page

* Renaming and bringing back eslint comment

* Rename id to pageId

* Add missing id in AlertsTableFlyoutBaseProps interface

* Fixing the types to match reality. Not everything is string[]

* Splitting ObservabilityActions from table t-grid; adding tests for AlertsFlyoutBody and ObservabilityActions

* Fixing more mappings

* removing unnecessary reduce

* Changing to unknown type instead of listing all the possible options to match the ES Typescript interface.

* reverting all the type changes

* reverting all the type changes

* Forcing the fixture data to be TimelineNonEcsData[]

* Aliasing id to pageId

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Chris Cowan <chris@elastic.co>
This commit is contained in:
Maryam Saeidi 2022-08-03 22:06:41 +02:00 committed by GitHub
parent 02e92387cb
commit 7f46fde188
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 488 additions and 199 deletions

View file

@ -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 { render } from '../../../../utils/test_helper';
import React from 'react';
import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting';
import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/observability_rule_type_registry_mock';
import AlertsFlyoutBody from './alerts_flyout_body';
import { inventoryThresholdAlert } from '../../../../rules/fixtures/example_alerts';
import { parseAlert } from '../parse_alert';
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/types';
describe('AlertsFlyoutBody', () => {
jest
.spyOn(useUiSettingHook, 'useUiSetting')
.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');
const observabilityRuleTypeRegistryMock = createObservabilityRuleTypeRegistryMock();
const setup = (id: string) => {
const dataFieldEs = inventoryThresholdAlert.reduce(
(acc, d) => ({ ...acc, [d.field]: d.value }),
{}
);
const alert = parseAlert(observabilityRuleTypeRegistryMock)(dataFieldEs);
return render(<AlertsFlyoutBody alert={alert} id={id} />);
};
it('should render View rule detail link', async () => {
const flyout = setup('test');
expect(flyout.getByTestId('viewRuleDetailsFlyout')).toBeInTheDocument();
});
it('should NOT render View rule detail link for RULE_DETAILS_PAGE_ID', async () => {
const flyout = setup(RULE_DETAILS_PAGE_ID);
expect(flyout.queryByTestId('viewRuleDetailsFlyout')).not.toBeInTheDocument();
});
});

View file

@ -26,20 +26,23 @@ import {
} from '@kbn/rule-data-utils';
import moment from 'moment-timezone';
import { useKibana, useUiSetting } from '@kbn/kibana-react-plugin/public';
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/types';
import { asDuration } from '../../../../../common/utils/formatters';
import { translations, paths } from '../../../../config';
import { AlertStatusIndicator } from '../../../../components/shared/alert_status_indicator';
import { FlyoutProps } from './types';
// eslint-disable-next-line import/no-default-export
export default function AlertsFlyoutBody(props: FlyoutProps) {
const alert = props.alert;
export default function AlertsFlyoutBody({ alert, id: pageId }: FlyoutProps) {
const { services } = useKibana();
const { http } = services;
const dateFormat = useUiSetting<string>('dateFormat');
const prepend = http?.basePath.prepend;
const ruleId = get(props.alert.fields, ALERT_RULE_UUID) ?? null;
const linkToRule = ruleId && prepend ? prepend(paths.observability.ruleDetails(ruleId)) : null;
const ruleId = get(alert.fields, ALERT_RULE_UUID) ?? null;
const linkToRule =
pageId !== RULE_DETAILS_PAGE_ID && ruleId && prepend
? prepend(paths.observability.ruleDetails(ruleId))
: null;
const overviewListItems = [
{
title: translations.alertsFlyout.statusLabel,

View file

@ -24,7 +24,7 @@ export const useToGetInternalFlyout = (
const alert = parseAlert(observabilityRuleTypeRegistry)(
props.alert as unknown as Record<string, unknown>
);
return <AlertsFlyoutBody alert={alert} />;
return <AlertsFlyoutBody alert={alert} id={props.id} />;
},
[observabilityRuleTypeRegistry]
);

View file

@ -9,4 +9,5 @@ import { TopAlert } from '../../containers';
export interface FlyoutProps {
alert: TopAlert;
id?: string;
}

View file

@ -0,0 +1,74 @@
/*
* 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 { act } from '@testing-library/react-hooks';
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
import React from 'react';
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
import { ObservabilityActions, ObservabilityActionsProps } from './observability_actions';
import { inventoryThresholdAlert } from '../../../rules/fixtures/example_alerts';
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/types';
import { createObservabilityRuleTypeRegistryMock } from '../../../rules/observability_rule_type_registry_mock';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
const mockUseKibanaReturnValue = kibanaStartMock.startContract();
jest.mock('../../../utils/kibana_react', () => ({
__esModule: true,
useKibana: jest.fn(() => mockUseKibanaReturnValue),
}));
jest.mock('../../../hooks/use_get_user_cases_permissions', () => ({
useGetUserCasesPermissions: jest.fn(() => ({})),
}));
describe('ObservabilityActions component', () => {
const setup = async (pageId: string) => {
const props: ObservabilityActionsProps = {
eventId: '6d4c6d74-d51a-495c-897d-88ced3b95e30',
ecsData: {
_id: '6d4c6d74-d51a-495c-897d-88ced3b95e30',
_index: '.internal.alerts-observability.metrics.alerts-default-000001',
},
data: inventoryThresholdAlert as unknown as TimelineNonEcsData[],
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
setEventsDeleted: jest.fn(),
setFlyoutAlert: jest.fn(),
id: pageId,
};
const wrapper = mountWithIntl(<ObservabilityActions {...props} />);
await act(async () => {
await nextTick();
wrapper.update();
});
return wrapper;
};
it('should hide "View rule details" menu item for rule page id', async () => {
const wrapper = await setup(RULE_DETAILS_PAGE_ID);
wrapper.find('[data-test-subj="alertsTableRowActionMore"]').hostNodes().simulate('click');
expect(wrapper.find('[data-test-subj~="viewRuleDetails"]').hostNodes().length).toBe(0);
expect(wrapper.find('[data-test-subj~="viewAlertDetails"]').hostNodes().length).toBe(1);
});
it('should show "View rule details" menu item', async () => {
const wrapper = await setup('nothing');
wrapper.find('[data-test-subj="alertsTableRowActionMore"]').hostNodes().simulate('click');
expect(wrapper.find('[data-test-subj~="viewRuleDetails"]').hostNodes().length).toBe(1);
expect(wrapper.find('[data-test-subj~="viewAlertDetails"]').hostNodes().length).toBe(1);
});
it('should create a valid link for rule details page', async () => {
const wrapper = await setup('nothing');
wrapper.find('[data-test-subj="alertsTableRowActionMore"]').hostNodes().simulate('click');
expect(wrapper.find('[data-test-subj~="viewRuleDetails"]').hostNodes().length).toBe(1);
expect(wrapper.find('[data-test-subj~="viewRuleDetails"]').hostNodes().prop('href')).toBe(
'/app/observability/alerts/rules/06f53080-0f91-11ed-9d86-013908b232ef'
);
});
});

View file

@ -0,0 +1,206 @@
/*
* 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 {
EuiButtonIcon,
EuiFlexItem,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import React, { useMemo, useState, useCallback } from 'react';
import { CaseAttachments } from '@kbn/cases-plugin/public';
import { CommentType } from '@kbn/cases-plugin/common';
import type { ActionProps } from '@kbn/timelines-plugin/common';
import { useKibana } from '../../../utils/kibana_react';
import { observabilityFeatureId } from '../../../../common';
import { useGetUserCasesPermissions } from '../../../hooks/use_get_user_cases_permissions';
import { parseAlert } from './parse_alert';
import { translations, paths } from '../../../config';
import {
ADD_TO_EXISTING_CASE,
ADD_TO_NEW_CASE,
} from '../containers/alerts_table_t_grid/translations';
import { ObservabilityAppServices } from '../../../application/types';
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/types';
import type { TopAlert } from '../containers/alerts_page/alerts_page';
import { ObservabilityRuleTypeRegistry } from '../../..';
export type ObservabilityActionsProps = Pick<
ActionProps,
'data' | 'eventId' | 'ecsData' | 'setEventsDeleted'
> & {
setFlyoutAlert: React.Dispatch<React.SetStateAction<TopAlert | undefined>>;
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
id?: string;
};
export function ObservabilityActions({
data,
eventId,
ecsData,
id: pageId,
observabilityRuleTypeRegistry,
setFlyoutAlert,
}: ObservabilityActionsProps) {
const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {});
const [openActionsPopoverId, setActionsPopover] = useState(null);
const { cases, http } = useKibana<ObservabilityAppServices>().services;
const parseObservabilityAlert = useMemo(
() => parseAlert(observabilityRuleTypeRegistry),
[observabilityRuleTypeRegistry]
);
const alert = parseObservabilityAlert(dataFieldEs);
const closeActionsPopover = useCallback(() => {
setActionsPopover(null);
}, []);
const toggleActionsPopover = useCallback((id) => {
setActionsPopover((current) => (current ? null : id));
}, []);
const userCasesPermissions = useGetUserCasesPermissions();
const ruleId = alert.fields['kibana.alert.rule.uuid'] ?? null;
const linkToRule =
pageId !== RULE_DETAILS_PAGE_ID && ruleId
? http.basePath.prepend(paths.observability.ruleDetails(ruleId))
: null;
const caseAttachments: CaseAttachments = useMemo(() => {
return ecsData?._id
? [
{
alertId: ecsData?._id ?? '',
index: ecsData?._index ?? '',
owner: observabilityFeatureId,
type: CommentType.alert,
rule: cases.helpers.getRuleIdFromEvent({ ecs: ecsData, data: data ?? [] }),
},
]
: [];
}, [ecsData, cases.helpers, data]);
const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout();
const selectCaseModal = cases.hooks.getUseCasesAddToExistingCaseModal();
const handleAddToNewCaseClick = useCallback(() => {
createCaseFlyout.open({ attachments: caseAttachments });
closeActionsPopover();
}, [createCaseFlyout, caseAttachments, closeActionsPopover]);
const handleAddToExistingCaseClick = useCallback(() => {
selectCaseModal.open({ attachments: caseAttachments });
closeActionsPopover();
}, [caseAttachments, closeActionsPopover, selectCaseModal]);
const actionsMenuItems = useMemo(() => {
return [
...(userCasesPermissions.create && userCasesPermissions.read
? [
<EuiContextMenuItem
data-test-subj="add-to-existing-case-action"
onClick={handleAddToExistingCaseClick}
size="s"
>
{ADD_TO_EXISTING_CASE}
</EuiContextMenuItem>,
<EuiContextMenuItem
data-test-subj="add-to-new-case-action"
onClick={handleAddToNewCaseClick}
size="s"
>
{ADD_TO_NEW_CASE}
</EuiContextMenuItem>,
]
: []),
...(!!linkToRule
? [
<EuiContextMenuItem
key="viewRuleDetails"
data-test-subj="viewRuleDetails"
href={linkToRule}
>
{translations.alertsTable.viewRuleDetailsButtonText}
</EuiContextMenuItem>,
]
: []),
...[
<EuiContextMenuItem
key="viewAlertDetails"
data-test-subj="viewAlertDetails"
onClick={() => {
closeActionsPopover();
setFlyoutAlert(alert);
}}
>
{translations.alertsTable.viewAlertDetailsButtonText}
</EuiContextMenuItem>,
],
];
}, [
userCasesPermissions.create,
userCasesPermissions.read,
handleAddToExistingCaseClick,
handleAddToNewCaseClick,
linkToRule,
alert,
setFlyoutAlert,
closeActionsPopover,
]);
const actionsToolTip =
actionsMenuItems.length <= 0
? translations.alertsTable.notEnoughPermissions
: translations.alertsTable.moreActionsTextLabel;
return (
<>
<EuiFlexItem>
<EuiToolTip content={translations.alertsTable.viewInAppTextLabel}>
<EuiButtonIcon
size="s"
href={http.basePath.prepend(alert.link ?? '')}
iconType="eye"
color="text"
aria-label={translations.alertsTable.viewInAppTextLabel}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiPopover
button={
<EuiToolTip content={actionsToolTip}>
<EuiButtonIcon
display="empty"
size="s"
color="text"
iconType="boxesHorizontal"
aria-label={actionsToolTip}
onClick={() => toggleActionsPopover(eventId)}
data-test-subj="alertsTableRowActionMore"
/>
</EuiToolTip>
}
isOpen={openActionsPopoverId === eventId}
closePopover={closeActionsPopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel size="s" items={actionsMenuItems} />
</EuiPopover>
</EuiFlexItem>
</>
);
}

View file

@ -23,16 +23,7 @@ import {
ALERT_START,
} from '@kbn/rule-data-utils';
import {
EuiButtonIcon,
EuiDataGridColumn,
EuiFlexGroup,
EuiFlexItem,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import { EuiDataGridColumn, EuiFlexGroup } from '@elastic/eui';
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
@ -53,8 +44,6 @@ import type {
ControlColumnProps,
RowRenderer,
} from '@kbn/timelines-plugin/common';
import { CaseAttachments } from '@kbn/cases-plugin/public';
import { CommentType } from '@kbn/cases-plugin/common';
import { getAlertsPermissions } from '../../../../hooks/use_alert_permission';
import type { TopAlert } from '../alerts_page/alerts_page';
@ -63,13 +52,15 @@ import { getRenderCellValue } from '../../components/render_cell_value';
import { observabilityAppId, observabilityFeatureId } from '../../../../../common';
import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions';
import { usePluginContext } from '../../../../hooks/use_plugin_context';
import { LazyAlertsFlyout, ObservabilityRuleTypeRegistry } from '../../../..';
import { parseAlert } from '../../components/parse_alert';
import { translations, paths } from '../../../../config';
import { LazyAlertsFlyout } from '../../../..';
import { translations } from '../../../../config';
import { addDisplayNames } from './add_display_names';
import { ADD_TO_EXISTING_CASE, ADD_TO_NEW_CASE } from './translations';
import { ObservabilityAppServices } from '../../../../application/types';
import { useBulkAddToCaseActions } from '../../../../hooks/use_alert_bulk_case_actions';
import {
ObservabilityActions,
ObservabilityActionsProps,
} from '../../components/observability_actions';
interface AlertsTableTGridProps {
indexNames: string[];
@ -82,14 +73,6 @@ interface AlertsTableTGridProps {
itemsPerPage?: number;
}
export type ObservabilityActionsProps = Pick<
ActionProps,
'data' | 'eventId' | 'ecsData' | 'setEventsDeleted'
> & {
setFlyoutAlert: React.Dispatch<React.SetStateAction<TopAlert | undefined>>;
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
};
const EventsThContent = styled.div.attrs(({ className = '' }) => ({
className: `siemEventsTable__thContent ${className}`,
}))<{ textAlign?: string; width?: number }>`
@ -146,164 +129,6 @@ const NO_ROW_RENDER: RowRenderer[] = [];
const trailingControlColumns: never[] = [];
export function ObservabilityActions({
data,
eventId,
ecsData,
observabilityRuleTypeRegistry,
setFlyoutAlert,
}: ObservabilityActionsProps) {
const dataFieldEs = data.reduce((acc, d) => ({ ...acc, [d.field]: d.value }), {});
const [openActionsPopoverId, setActionsPopover] = useState(null);
const { cases, http } = useKibana<ObservabilityAppServices>().services;
const parseObservabilityAlert = useMemo(
() => parseAlert(observabilityRuleTypeRegistry),
[observabilityRuleTypeRegistry]
);
const alert = parseObservabilityAlert(dataFieldEs);
const closeActionsPopover = useCallback(() => {
setActionsPopover(null);
}, []);
const toggleActionsPopover = useCallback((id) => {
setActionsPopover((current) => (current ? null : id));
}, []);
const userCasesPermissions = useGetUserCasesPermissions();
const ruleId = alert.fields['kibana.alert.rule.uuid'] ?? null;
const linkToRule = ruleId ? http.basePath.prepend(paths.observability.ruleDetails(ruleId)) : null;
const caseAttachments: CaseAttachments = useMemo(() => {
return ecsData?._id
? [
{
alertId: ecsData?._id ?? '',
index: ecsData?._index ?? '',
owner: observabilityFeatureId,
type: CommentType.alert,
rule: cases.helpers.getRuleIdFromEvent({ ecs: ecsData, data: data ?? [] }),
},
]
: [];
}, [ecsData, cases.helpers, data]);
const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout();
const selectCaseModal = cases.hooks.getUseCasesAddToExistingCaseModal();
const handleAddToNewCaseClick = useCallback(() => {
createCaseFlyout.open({ attachments: caseAttachments });
closeActionsPopover();
}, [createCaseFlyout, caseAttachments, closeActionsPopover]);
const handleAddToExistingCaseClick = useCallback(() => {
selectCaseModal.open({ attachments: caseAttachments });
closeActionsPopover();
}, [caseAttachments, closeActionsPopover, selectCaseModal]);
const actionsMenuItems = useMemo(() => {
return [
...(userCasesPermissions.create && userCasesPermissions.read
? [
<EuiContextMenuItem
data-test-subj="add-to-existing-case-action"
onClick={handleAddToExistingCaseClick}
size="s"
>
{ADD_TO_EXISTING_CASE}
</EuiContextMenuItem>,
<EuiContextMenuItem
data-test-subj="add-to-new-case-action"
onClick={handleAddToNewCaseClick}
size="s"
>
{ADD_TO_NEW_CASE}
</EuiContextMenuItem>,
]
: []),
...(!!linkToRule
? [
<EuiContextMenuItem
key="viewRuleDetails"
data-test-subj="viewRuleDetails"
href={linkToRule}
>
{translations.alertsTable.viewRuleDetailsButtonText}
</EuiContextMenuItem>,
]
: []),
...[
<EuiContextMenuItem
key="viewAlertDetails"
data-test-subj="viewAlertDetails"
onClick={() => {
closeActionsPopover();
setFlyoutAlert(alert);
}}
>
{translations.alertsTable.viewAlertDetailsButtonText}
</EuiContextMenuItem>,
],
];
}, [
userCasesPermissions.create,
userCasesPermissions.read,
handleAddToExistingCaseClick,
handleAddToNewCaseClick,
linkToRule,
alert,
setFlyoutAlert,
closeActionsPopover,
]);
const actionsToolTip =
actionsMenuItems.length <= 0
? translations.alertsTable.notEnoughPermissions
: translations.alertsTable.moreActionsTextLabel;
return (
<>
<EuiFlexItem>
<EuiToolTip content={translations.alertsTable.viewInAppTextLabel}>
<EuiButtonIcon
size="s"
href={http.basePath.prepend(alert.link ?? '')}
iconType="eye"
color="text"
aria-label={translations.alertsTable.viewInAppTextLabel}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiPopover
button={
<EuiToolTip content={actionsToolTip}>
<EuiButtonIcon
display="empty"
size="s"
color="text"
iconType="boxesHorizontal"
aria-label={actionsToolTip}
onClick={() => toggleActionsPopover(eventId)}
data-test-subj="alertsTableRowActionMore"
/>
</EuiToolTip>
}
isOpen={openActionsPopoverId === eventId}
closePopover={closeActionsPopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel size="s" items={actionsMenuItems} />
</EuiPopover>
</EuiFlexItem>
</>
);
}
const FIELDS_WITHOUT_CELL_ACTIONS = [
'@timestamp',
'signal.rule.risk_score',

View file

@ -7,8 +7,8 @@
import React from 'react';
import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy';
import { ObservabilityRuleTypeRegistry } from '../../../../rules/create_observability_rule_type_registry';
import { ObservabilityActions } from './alerts_table_t_grid';
import type { ObservabilityActionsProps } from './alerts_table_t_grid';
import { ObservabilityActions } from '../../components/observability_actions';
import type { ObservabilityActionsProps } from '../../components/observability_actions';
const buildData = (alerts: EcsFieldsResponse): ObservabilityActionsProps['data'] => {
return Object.entries(alerts).reduce<ObservabilityActionsProps['data']>(
@ -19,12 +19,17 @@ const buildData = (alerts: EcsFieldsResponse): ObservabilityActionsProps['data']
const fakeSetEventsDeleted = () => [];
export const getRowActions = (observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry) => {
return () => ({
renderCustomActionsRow: (alert: EcsFieldsResponse, setFlyoutAlert: (data: unknown) => void) => {
renderCustomActionsRow: (
alert: EcsFieldsResponse,
setFlyoutAlert: (data: unknown, id?: string) => void,
id?: string
) => {
return (
<ObservabilityActions
data={buildData(alert)}
eventId={alert._id}
ecsData={{ _id: alert._id, _index: alert._index }}
id={id}
observabilityRuleTypeRegistry={observabilityRuleTypeRegistry}
setEventsDeleted={fakeSetEventsDeleted}
setFlyoutAlert={setFlyoutAlert}

View file

@ -37,7 +37,12 @@ import { RuleDefinitionProps } from '@kbn/triggers-actions-ui-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { DeleteModalConfirmation } from './components/delete_modal_confirmation';
import { CenterJustifiedSpinner } from './components/center_justified_spinner';
import { RuleDetailsPathParams, EVENT_LOG_LIST_TAB, ALERT_LIST_TAB } from './types';
import {
RuleDetailsPathParams,
EVENT_LOG_LIST_TAB,
ALERT_LIST_TAB,
RULE_DETAILS_PAGE_ID,
} from './types';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useFetchRule } from '../../hooks/use_fetch_rule';
@ -155,7 +160,7 @@ export function RuleDetailsPage() {
const alertStateProps = {
alertsTableConfigurationRegistry,
configurationId: observabilityFeatureId,
id: `case-details-alerts-o11y`,
id: RULE_DETAILS_PAGE_ID,
flyoutSize: 's' as EuiFlyoutSize,
featureIds: [features] as AlertConsumers[],
query: {

View file

@ -80,3 +80,4 @@ export interface ActionsProps {
export const EVENT_LOG_LIST_TAB = 'rule_event_log_list';
export const ALERT_LIST_TAB = 'rule_alert_list';
export const EVENT_ERROR_LOG_TAB = 'rule_error_log_list';
export const RULE_DETAILS_PAGE_ID = 'rule-details-alerts-o11y';

View file

@ -0,0 +1,117 @@
/*
* 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 const inventoryThresholdAlert = [
{
field: 'kibana.alert.workflow_status',
value: ['open'],
},
{
field: 'kibana.alert.status',
value: ['active'],
},
{
field: 'kibana.alert.rule.uuid',
value: ['06f53080-0f91-11ed-9d86-013908b232ef'],
},
{
field: 'kibana.alert.reason',
value: ['CPU usage is 106.5% in the last 1 min for host-0. Alert when > 1%.'],
},
{
field: 'kibana.alert.rule.producer',
value: ['infrastructure'],
},
{
field: 'kibana.alert.rule.consumer',
value: ['alerts'],
},
{
field: 'kibana.alert.rule.category',
value: ['Inventory'],
},
{
field: 'kibana.alert.start',
value: ['2022-07-29T22:51:51.904Z'],
},
{
field: 'event.action',
value: ['open'],
},
{
field: 'kibana.alert.rule.rule_type_id',
value: ['metrics.alert.inventory.threshold'],
},
{
field: 'kibana.alert.duration.us',
value: [0],
},
{
field: '@timestamp',
value: ['2022-07-29T22:51:51.904Z'],
},
{
field: 'kibana.alert.instance.id',
value: ['host-0'],
},
{
field: 'kibana.alert.rule.name',
value: ['Test Alert'],
},
{
field: 'kibana.space_ids',
value: ['default'],
},
{
field: 'kibana.alert.uuid',
value: ['6d4c6d74-d51a-495c-897d-88ced3b95e30'],
},
{
field: 'kibana.alert.rule.execution.uuid',
value: ['b8cccff3-2d3e-41d0-a91e-6aae8efce8ba'],
},
{
field: 'kibana.version',
value: ['8.5.0'],
},
{
field: 'event.kind',
value: ['signal'],
},
{
field: 'kibana.alert.rule.parameters',
value: [
{
sourceId: 'default',
criteria: [
{
comparator: '>',
timeSize: 1,
metric: 'cpu',
threshold: [1],
customMetric: {
field: '',
aggregation: 'avg',
id: 'alert-custom-metric',
type: 'custom',
},
timeUnit: 'm',
},
],
nodeType: 'host',
},
],
},
{
field: '_id',
value: '6d4c6d74-d51a-495c-897d-88ced3b95e30',
},
{
field: '_index',
value: '.internal.alerts-observability.metrics.alerts-default-000001',
},
];

View file

@ -10,10 +10,12 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { ObservabilityPublicPluginsStart } from '../plugin';
export type StartServices = CoreStart &
ObservabilityPublicPluginsStart & {
export type StartServices<AdditionalServices extends object = {}> = CoreStart &
ObservabilityPublicPluginsStart &
AdditionalServices & {
storage: Storage;
};
const useTypedKibana = () => useKibana<StartServices>();
const useTypedKibana = <AdditionalServices extends object = {}>() =>
useKibana<StartServices<AdditionalServices>>();
export { useTypedKibana as useKibana };

View file

@ -36,6 +36,7 @@ interface AlertsFlyoutProps {
isLoading: boolean;
onClose: () => void;
onPaginate: (pageIndex: number) => void;
id?: string;
}
export const AlertsFlyout: React.FunctionComponent<AlertsFlyoutProps> = ({
alert,
@ -46,6 +47,7 @@ export const AlertsFlyout: React.FunctionComponent<AlertsFlyoutProps> = ({
isLoading,
onClose,
onPaginate,
id,
}: AlertsFlyoutProps) => {
const {
header: Header,
@ -60,9 +62,10 @@ export const AlertsFlyout: React.FunctionComponent<AlertsFlyoutProps> = ({
const passedProps = useMemo(
() => ({
alert,
id,
isLoading,
}),
[alert, isLoading]
[alert, id, isLoading]
);
const FlyoutBody = useCallback(

View file

@ -156,7 +156,7 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
</EuiFlexItem>
)}
{renderCustomActionsRow &&
renderCustomActionsRow(alerts[visibleRowIndex], handleFlyoutAlert)}
renderCustomActionsRow(alerts[visibleRowIndex], handleFlyoutAlert, props.id)}
</EuiFlexGroup>
);
},
@ -173,9 +173,10 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
}, [
actionsColumnWidth,
alerts,
handleFlyoutAlert,
getBulkActionsLeadingControlColumn,
handleFlyoutAlert,
isBulkActionsColumnActive,
props.id,
props.leadingControlColumns,
props.showExpandToDetails,
renderCustomActionsRow,
@ -242,6 +243,7 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab
flyoutIndex={flyoutAlertIndex + pagination.pageIndex * pagination.pageSize}
onPaginate={onPaginateFlyout}
isLoading={isLoading}
id={props.id}
/>
)}
</Suspense>

View file

@ -245,6 +245,7 @@ const AlertsTableState = ({
flyoutSize,
pageSize: pagination.pageSize,
pageSizeOptions: [10, 20, 50, 100],
id,
leadingControlColumns: [],
showExpandToDetails,
trailingControlColumns: [],
@ -258,6 +259,7 @@ const AlertsTableState = ({
columns,
flyoutSize,
pagination.pageSize,
id,
showExpandToDetails,
useFetchAlertsData,
updatedAt,

View file

@ -419,6 +419,7 @@ export interface AlertsTableProps {
flyoutSize?: EuiFlyoutSize;
pageSize: number;
pageSizeOptions: number[];
id?: string;
leadingControlColumns: EuiDataGridControlColumn[];
showExpandToDetails: boolean;
trailingControlColumns: EuiDataGridControlColumn[];
@ -443,6 +444,7 @@ export type AlertTableFlyoutComponent =
export interface AlertsTableFlyoutBaseProps {
alert: EcsFieldsResponse;
isLoading: boolean;
id?: string;
}
export interface BulkActionsConfig {
@ -470,7 +472,8 @@ export interface AlertsTableConfigurationRegistry {
useActionsColumn?: () => {
renderCustomActionsRow: (
alert: EcsFieldsResponse,
setFlyoutAlert: (data: unknown) => void
setFlyoutAlert: (data: unknown) => void,
id?: string
) => JSX.Element;
width?: number;
};