mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[RAM] Replace alerts list with table in rule details page (#172302)
Closes #168472
## Summary
Replaces the old alerts list with the alerts table in the rule details
page, only for supported rule types.
<img width="1464" alt="image"
src="e556438a
-3c01-4460-bfa8-2ff32d15a077">
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
---------
Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>
Co-authored-by: Maryam Saeidi <maryam.saeidi@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2ada7f8153
commit
7803c45878
16 changed files with 496 additions and 39 deletions
|
@ -24,6 +24,7 @@ export const AlertConsumers = {
|
|||
SIEM: 'siem',
|
||||
UPTIME: 'uptime',
|
||||
ML: 'ml',
|
||||
STACK_ALERTS: 'stackAlerts',
|
||||
} as const;
|
||||
export type AlertConsumers = typeof AlertConsumers[keyof typeof AlertConsumers];
|
||||
export type STATUS_VALUES = 'open' | 'acknowledged' | 'closed' | 'in-progress'; // TODO: remove 'in-progress' after migration to 'acknowledged'
|
||||
|
|
|
@ -29,8 +29,7 @@ export type DeprecatedCellValueElementProps = EuiDataGridCellValueElementProps &
|
|||
isTimeline?: boolean; // Default cell renderer is used for both the alert table and timeline. This allows us to cheaply separate concerns
|
||||
linkValues: string[] | undefined;
|
||||
rowRenderers?: DeprecatedRowRenderer[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setFlyoutAlert?: (data: any) => void;
|
||||
setFlyoutAlert?: (alertId: string) => void;
|
||||
scopeId: string;
|
||||
truncate?: boolean;
|
||||
key?: string;
|
||||
|
|
10
x-pack/plugins/triggers_actions_ui/common/alert_config.ts
Normal file
10
x-pack/plugins/triggers_actions_ui/common/alert_config.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
export const ALERT_TABLE_GENERIC_CONFIG_ID = `${AlertConsumers.STACK_ALERTS}-generic-alert-table`;
|
|
@ -13,3 +13,4 @@ export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'
|
|||
export * from './parse_interval';
|
||||
export * from './experimental_features';
|
||||
export * from './normalized_field_types';
|
||||
export * from './alert_config';
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"kibanaUtils",
|
||||
"savedObjects",
|
||||
"unifiedSearch",
|
||||
"fieldFormats",
|
||||
"dataViews",
|
||||
"dataViewEditor",
|
||||
"alerting",
|
||||
|
|
|
@ -23,6 +23,7 @@ import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/publi
|
|||
import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
||||
|
@ -73,6 +74,7 @@ export interface TriggersAndActionsUiServices extends CoreStart {
|
|||
licensing: LicensingPluginStart;
|
||||
expressions: ExpressionsStart;
|
||||
isServerless: boolean;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
}
|
||||
|
||||
export const renderApp = (deps: TriggersAndActionsUiServices) => {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash';
|
||||
import React from 'react';
|
||||
import { type EuiDataGridColumn, EuiDescriptionList, EuiPanel, EuiTitle } from '@elastic/eui';
|
||||
import { ALERT_RULE_NAME } from '@kbn/rule-data-utils';
|
||||
import { AlertsTableFlyoutBaseProps, AlertTableFlyoutComponent } from '../../../..';
|
||||
import { RegisterFormatter } from '../cells/render_cell_value';
|
||||
|
||||
const FlyoutHeader: AlertTableFlyoutComponent = ({ alert }: AlertsTableFlyoutBaseProps) => {
|
||||
const name = alert[ALERT_RULE_NAME];
|
||||
return (
|
||||
<EuiTitle size="s">
|
||||
<h3>{name}</h3>
|
||||
</EuiTitle>
|
||||
);
|
||||
};
|
||||
|
||||
export const getDefaultAlertFlyout =
|
||||
(columns: EuiDataGridColumn[], formatter: RegisterFormatter) => () => {
|
||||
const FlyoutBody: AlertTableFlyoutComponent = ({ alert }: AlertsTableFlyoutBaseProps) => (
|
||||
<EuiPanel>
|
||||
<EuiDescriptionList
|
||||
listItems={columns.map((column) => {
|
||||
const value = get(alert, column.id)?.[0];
|
||||
|
||||
return {
|
||||
title: column.displayAsText as string,
|
||||
description: value != null ? formatter(column.id, value) : '—',
|
||||
};
|
||||
})}
|
||||
type="column"
|
||||
columnWidths={[1, 3]}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
return {
|
||||
body: FlyoutBody,
|
||||
header: FlyoutHeader,
|
||||
footer: null,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { isEmpty } from 'lodash';
|
||||
import React, { type ReactNode } from 'react';
|
||||
import { ALERT_DURATION, TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
FIELD_FORMAT_IDS,
|
||||
FieldFormatParams,
|
||||
FieldFormatsRegistry,
|
||||
} from '@kbn/field-formats-plugin/common';
|
||||
import { GetRenderCellValue } from '../../../../types';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
interface Props {
|
||||
columnId: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export const getMappedNonEcsValue = ({
|
||||
data,
|
||||
fieldName,
|
||||
}: {
|
||||
data: any[];
|
||||
fieldName: string;
|
||||
}): string[] | undefined => {
|
||||
const item = data.find((d) => d.field === fieldName);
|
||||
if (item != null && item.value != null) {
|
||||
return item.value;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getRenderValue = (mappedNonEcsValue: any) => {
|
||||
const value = Array.isArray(mappedNonEcsValue) ? mappedNonEcsValue.join() : mappedNonEcsValue;
|
||||
|
||||
if (!isEmpty(value)) {
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return '—';
|
||||
};
|
||||
|
||||
export const getRenderCellValue = (fieldFormats: FieldFormatsRegistry): GetRenderCellValue => {
|
||||
const alertValueFormatter = getAlertFormatters(fieldFormats);
|
||||
|
||||
return () =>
|
||||
(props): ReactNode => {
|
||||
const { columnId, data } = props as Props;
|
||||
if (data == null) return null;
|
||||
|
||||
const mappedNonEcsValue = getMappedNonEcsValue({
|
||||
data,
|
||||
fieldName: columnId,
|
||||
});
|
||||
const value = getRenderValue(mappedNonEcsValue);
|
||||
|
||||
return alertValueFormatter(columnId, value);
|
||||
};
|
||||
};
|
||||
|
||||
const defaultParam: Record<string, FieldFormatParams> = {
|
||||
[FIELD_FORMAT_IDS.DURATION]: {
|
||||
inputFormat: 'milliseconds',
|
||||
outputFormat: 'humanizePrecise',
|
||||
},
|
||||
[FIELD_FORMAT_IDS.NUMBER]: {
|
||||
pattern: '00.00',
|
||||
},
|
||||
};
|
||||
|
||||
export const getFieldFormatterProvider =
|
||||
(fieldFormats: FieldFormatsRegistry) =>
|
||||
(fieldType: FIELD_FORMAT_IDS, params?: FieldFormatParams) => {
|
||||
const fieldFormatter = fieldFormats.deserialize({
|
||||
id: fieldType,
|
||||
params: params ?? defaultParam[fieldType],
|
||||
});
|
||||
return fieldFormatter.convert.bind(fieldFormatter);
|
||||
};
|
||||
|
||||
export function useFieldFormatter(fieldType: FIELD_FORMAT_IDS) {
|
||||
const { fieldFormats } = useKibana().services;
|
||||
return getFieldFormatterProvider(fieldFormats as FieldFormatsRegistry)(fieldType);
|
||||
}
|
||||
|
||||
export function getAlertFormatters(fieldFormats: FieldFormatsRegistry) {
|
||||
const getFormatter = getFieldFormatterProvider(fieldFormats);
|
||||
|
||||
return (columnId: string, value: any): React.ReactElement => {
|
||||
switch (columnId) {
|
||||
case TIMESTAMP:
|
||||
return <>{getFormatter(FIELD_FORMAT_IDS.DATE)(value)}</>;
|
||||
case ALERT_DURATION:
|
||||
return (
|
||||
<>
|
||||
{getFormatter(FIELD_FORMAT_IDS.DURATION, {
|
||||
inputFormat: 'microseconds',
|
||||
outputFormat: 'humanizePrecise',
|
||||
})(value) || '--'}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return <>{value}</>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type RegisterFormatter = ReturnType<typeof getAlertFormatters>;
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 {
|
||||
ALERT_DURATION,
|
||||
ALERT_MAINTENANCE_WINDOW_IDS,
|
||||
ALERT_REASON,
|
||||
ALERT_STATUS,
|
||||
TIMESTAMP,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDefaultAlertFlyout } from './alerts_flyout/default_alerts_flyout';
|
||||
import { AlertActionsCell } from './row_actions/alert_actions_cell';
|
||||
import { AlertsTableConfigurationRegistry, RenderCustomActionsRowArgs } from '../../../types';
|
||||
import { getAlertFormatters, getRenderCellValue } from './cells/render_cell_value';
|
||||
import { ALERT_TABLE_GENERIC_CONFIG_ID } from '../../../../common';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
displayAsText: i18n.translate('xpack.triggersActionsUI.alertsTable.statusColumnDescription', {
|
||||
defaultMessage: 'Alert Status',
|
||||
}),
|
||||
id: ALERT_STATUS,
|
||||
initialWidth: 110,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
displayAsText: i18n.translate(
|
||||
'xpack.triggersActionsUI.alertsTable.lastUpdatedColumnDescription',
|
||||
{
|
||||
defaultMessage: 'Last updated',
|
||||
}
|
||||
),
|
||||
id: TIMESTAMP,
|
||||
initialWidth: 230,
|
||||
schema: 'datetime',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
displayAsText: i18n.translate('xpack.triggersActionsUI.alertsTable.durationColumnDescription', {
|
||||
defaultMessage: 'Duration',
|
||||
}),
|
||||
id: ALERT_DURATION,
|
||||
initialWidth: 116,
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
displayAsText: i18n.translate('xpack.triggersActionsUI.alertsTable.reasonColumnDescription', {
|
||||
defaultMessage: 'Reason',
|
||||
}),
|
||||
id: ALERT_REASON,
|
||||
linkField: '*',
|
||||
},
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
displayAsText: i18n.translate(
|
||||
'xpack.triggersActionsUI.alertsTable.maintenanceWindowsColumnDescription',
|
||||
{
|
||||
defaultMessage: 'Maintenance windows',
|
||||
}
|
||||
),
|
||||
id: ALERT_MAINTENANCE_WINDOW_IDS,
|
||||
schema: 'string',
|
||||
initialWidth: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export const getAlertsTableConfiguration = (
|
||||
fieldFormats: FieldFormatsRegistry
|
||||
): AlertsTableConfigurationRegistry => {
|
||||
return {
|
||||
id: ALERT_TABLE_GENERIC_CONFIG_ID,
|
||||
columns,
|
||||
getRenderCellValue: getRenderCellValue(fieldFormats),
|
||||
useInternalFlyout: getDefaultAlertFlyout(columns, getAlertFormatters(fieldFormats)),
|
||||
sort: [
|
||||
{
|
||||
[TIMESTAMP]: {
|
||||
order: 'desc' as SortOrder,
|
||||
},
|
||||
},
|
||||
],
|
||||
useActionsColumn: () => ({
|
||||
renderCustomActionsRow: (props: RenderCustomActionsRowArgs) => {
|
||||
return <AlertActionsCell {...props} />;
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -10,6 +10,7 @@ import { useFetchBrowserFieldCapabilities } from './use_fetch_browser_fields_cap
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { AlertsField } from '../../../../types';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
|
@ -48,7 +49,7 @@ describe('useFetchBrowserFieldCapabilities', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.mockReset();
|
||||
httpMock.mockClear();
|
||||
});
|
||||
|
||||
it('should not fetch for siem', () => {
|
||||
|
@ -98,4 +99,41 @@ describe('useFetchBrowserFieldCapabilities', () => {
|
|||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, browserFields, []]);
|
||||
});
|
||||
|
||||
it('should not fetch if the only featureId is not valid', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, {}, []]);
|
||||
});
|
||||
|
||||
it('should not fetch if all featureId are not valid', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts', 'tomato'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, {}, []]);
|
||||
});
|
||||
|
||||
it('should filter out the non valid feature id', async () => {
|
||||
const { waitForNextUpdate } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts', 'apm', 'logs'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(1);
|
||||
expect(httpMock).toHaveBeenCalledWith('/internal/rac/alerts/browser_fields', {
|
||||
query: { featureIds: ['apm', 'logs'] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { isValidFeatureId, ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { BASE_RAC_ALERTS_API_PATH, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FieldDescriptor } from '@kbn/data-views-plugin/server';
|
||||
|
@ -41,24 +41,29 @@ export const useFetchBrowserFieldCapabilities = ({
|
|||
);
|
||||
const [fields, setFields] = useState<FieldDescriptor[]>([]);
|
||||
|
||||
const getBrowserFieldInfo = useCallback(async (): Promise<{
|
||||
browserFields: BrowserFields;
|
||||
fields: FieldDescriptor[];
|
||||
}> => {
|
||||
if (!http) return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
const getBrowserFieldInfo = useCallback(
|
||||
async (
|
||||
validFeatureId: ValidFeatureId[]
|
||||
): Promise<{
|
||||
browserFields: BrowserFields;
|
||||
fields: FieldDescriptor[];
|
||||
}> => {
|
||||
if (!http) return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
|
||||
try {
|
||||
return await http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/browser_fields`,
|
||||
{
|
||||
query: { featureIds },
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
toasts.addDanger(ERROR_FETCH_BROWSER_FIELDS);
|
||||
return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
}
|
||||
}, [featureIds, http, toasts]);
|
||||
try {
|
||||
return await http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/browser_fields`,
|
||||
{
|
||||
query: { featureIds: validFeatureId },
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
toasts.addDanger(ERROR_FETCH_BROWSER_FIELDS);
|
||||
return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
}
|
||||
},
|
||||
[http, toasts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialBrowserFields) {
|
||||
|
@ -67,21 +72,26 @@ export const useFetchBrowserFieldCapabilities = ({
|
|||
setBrowserFields(initialBrowserFields);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoading !== undefined || featureIds.includes(INVALID_FEATURE_ID)) {
|
||||
const validFeatureIdTmp = featureIds.filter((fid) => isValidFeatureId(fid));
|
||||
if (
|
||||
isLoading !== undefined ||
|
||||
featureIds.includes(INVALID_FEATURE_ID) ||
|
||||
validFeatureIdTmp.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const callApi = async () => {
|
||||
const { browserFields: browserFieldsInfo, fields: newFields } = await getBrowserFieldInfo();
|
||||
const callApi = async (validFeatureId: ValidFeatureId[]) => {
|
||||
const { browserFields: browserFieldsInfo, fields: newFields } = await getBrowserFieldInfo(
|
||||
validFeatureId
|
||||
);
|
||||
setFields(newFields);
|
||||
setBrowserFields(browserFieldsInfo);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
callApi();
|
||||
callApi(validFeatureIdTmp);
|
||||
}, [getBrowserFieldInfo, isLoading, featureIds, initialBrowserFields]);
|
||||
|
||||
return [isLoading, browserFields, fields];
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiContextMenuPanel,
|
||||
EuiPopover,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getAlertsTableDefaultAlertActionsLazy } from '../../../../common/get_alerts_table_default_row_actions';
|
||||
import type { AlertActionsProps } from '../../../../types';
|
||||
|
||||
const actionsToolTip = i18n.translate('xpack.triggersActionsUI.alertsTable.moreActionsTextLabel', {
|
||||
defaultMessage: 'More actions',
|
||||
});
|
||||
|
||||
/**
|
||||
* The cell containing contextual actions for a single alert row in the table
|
||||
*/
|
||||
export function AlertActionsCell(alertActionsProps: AlertActionsProps) {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
|
||||
|
||||
const closeActionsPopover = () => {
|
||||
setIsPopoverOpen(false);
|
||||
};
|
||||
|
||||
const toggleActionsPopover = () => {
|
||||
setIsPopoverOpen(!isPopoverOpen);
|
||||
};
|
||||
|
||||
const DefaultRowActions = useMemo(
|
||||
() =>
|
||||
getAlertsTableDefaultAlertActionsLazy({
|
||||
key: 'defaultRowActions',
|
||||
onActionExecuted: closeActionsPopover,
|
||||
isAlertDetailsEnabled: false,
|
||||
...alertActionsProps,
|
||||
} as AlertActionsProps),
|
||||
[alertActionsProps]
|
||||
);
|
||||
|
||||
// TODO re-enable view in app when it works
|
||||
const actionsMenuItems = [DefaultRowActions];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
anchorPosition="downLeft"
|
||||
button={
|
||||
<EuiToolTip content={actionsToolTip}>
|
||||
<EuiButtonIcon
|
||||
aria-label={actionsToolTip}
|
||||
color="text"
|
||||
data-test-subj="alertsTableRowActionMore"
|
||||
display="empty"
|
||||
iconType="boxesHorizontal"
|
||||
onClick={toggleActionsPopover}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
}
|
||||
closePopover={closeActionsPopover}
|
||||
isOpen={isPopoverOpen}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
size="s"
|
||||
items={actionsMenuItems}
|
||||
data-test-subj="alertsTableActionsMenu"
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -5,10 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { lazy } from 'react';
|
||||
import React, { lazy, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiTabbedContent } from '@elastic/eui';
|
||||
import { AlertStatusValues } from '@kbn/alerting-plugin/common';
|
||||
import { AlertStatusValues, ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { ALERT_RULE_UUID, AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { ALERT_TABLE_GENERIC_CONFIG_ID } from '../../../../../common';
|
||||
import { AlertTableConfigRegistry } from '../../../alert_table_config_registry';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { Rule, RuleSummary, AlertStatus, RuleType } from '../../../../types';
|
||||
import {
|
||||
|
@ -34,6 +37,7 @@ import {
|
|||
const RuleEventLogList = lazy(() => import('./rule_event_log_list'));
|
||||
const RuleAlertList = lazy(() => import('./rule_alert_list'));
|
||||
const RuleDefinition = lazy(() => import('./rule_definition'));
|
||||
const AlertsTable = lazy(() => import('../../alerts_table/alerts_table_state'));
|
||||
|
||||
export type RuleComponentProps = {
|
||||
rule: Rule;
|
||||
|
@ -65,18 +69,22 @@ export function RuleComponent({
|
|||
durationEpoch = Date.now(),
|
||||
isLoadingChart,
|
||||
}: RuleComponentProps) {
|
||||
const { ruleTypeRegistry, actionTypeRegistry } = useKibana().services;
|
||||
const { ruleTypeRegistry, actionTypeRegistry, alertsTableConfigurationRegistry } =
|
||||
useKibana().services;
|
||||
|
||||
const alerts = Object.entries(ruleSummary.alerts)
|
||||
.map(([alertId, alert]) => alertToListItem(durationEpoch, alertId, alert))
|
||||
.sort((leftAlert, rightAlert) => leftAlert.sortPriority - rightAlert.sortPriority);
|
||||
|
||||
const onMuteAction = async (alert: AlertListItem) => {
|
||||
await (alert.isMuted
|
||||
? unmuteAlertInstance(rule, alert.alert)
|
||||
: muteAlertInstance(rule, alert.alert));
|
||||
requestRefresh();
|
||||
};
|
||||
const onMuteAction = useCallback(
|
||||
async (alert: AlertListItem) => {
|
||||
await (alert.isMuted
|
||||
? unmuteAlertInstance(rule, alert.alert)
|
||||
: muteAlertInstance(rule, alert.alert));
|
||||
requestRefresh();
|
||||
},
|
||||
[muteAlertInstance, requestRefresh, rule, unmuteAlertInstance]
|
||||
);
|
||||
|
||||
const healthColor = getRuleHealthColor(rule);
|
||||
const statusMessage = getRuleStatusMessage({
|
||||
|
@ -86,7 +94,25 @@ export function RuleComponent({
|
|||
executionStatusTranslations: rulesStatusesTranslationsMapping,
|
||||
});
|
||||
|
||||
const renderRuleAlertList = () => {
|
||||
const renderRuleAlertList = useCallback(() => {
|
||||
if (ruleType.hasAlertsMappings || ruleType.hasFieldsForAAD) {
|
||||
return (
|
||||
<AlertsTable
|
||||
id="rule-detail-alerts-table"
|
||||
configurationId={ALERT_TABLE_GENERIC_CONFIG_ID}
|
||||
alertsTableConfigurationRegistry={
|
||||
alertsTableConfigurationRegistry as AlertTableConfigRegistry
|
||||
}
|
||||
featureIds={
|
||||
(rule.consumer === ALERTS_FEATURE_ID
|
||||
? [ruleType.producer]
|
||||
: [rule.consumer]) as AlertConsumers[]
|
||||
}
|
||||
query={{ bool: { filter: { term: { [ALERT_RULE_UUID]: rule.id } } } }}
|
||||
showAlertStatusWithFlapping
|
||||
/>
|
||||
);
|
||||
}
|
||||
return suspendedComponentWithProps(
|
||||
RuleAlertList,
|
||||
'xl'
|
||||
|
@ -95,7 +121,17 @@ export function RuleComponent({
|
|||
readOnly,
|
||||
onMuteAction,
|
||||
});
|
||||
};
|
||||
}, [
|
||||
alerts,
|
||||
alertsTableConfigurationRegistry,
|
||||
onMuteAction,
|
||||
readOnly,
|
||||
rule.consumer,
|
||||
rule.id,
|
||||
ruleType.hasAlertsMappings,
|
||||
ruleType.hasFieldsForAAD,
|
||||
ruleType.producer,
|
||||
]);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
|||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { dashboardPluginMock } from '@kbn/dashboard-plugin/public/mocks';
|
||||
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
|
||||
import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/mocks';
|
||||
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -75,6 +76,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => {
|
|||
licensing: licensingPluginMock,
|
||||
expressions: expressionsPluginMock.createStartContract(),
|
||||
isServerless: false,
|
||||
fieldFormats: fieldFormatsServiceMock.createStartContract(),
|
||||
} as TriggersAndActionsUiServices;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import { DashboardStart } from '@kbn/dashboard-plugin/public';
|
|||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||
import { ExpressionsStart } from '@kbn/expressions-plugin/public';
|
||||
import { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
|
||||
import { getAlertsTableDefaultAlertActionsLazy } from './common/get_alerts_table_default_row_actions';
|
||||
import type { AlertActionsProps } from './types';
|
||||
import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar';
|
||||
|
@ -172,6 +173,7 @@ interface PluginsStart {
|
|||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
licensing: LicensingPluginStart;
|
||||
serverless?: ServerlessPluginStart;
|
||||
fieldFormats: FieldFormatsRegistry;
|
||||
}
|
||||
|
||||
export class Plugin
|
||||
|
@ -298,6 +300,7 @@ export class Plugin
|
|||
licensing: pluginsStart.licensing,
|
||||
expressions: pluginsStart.expressions,
|
||||
isServerless: !!pluginsStart.serverless,
|
||||
fieldFormats: pluginsStart.fieldFormats,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -363,7 +366,15 @@ export class Plugin
|
|||
};
|
||||
}
|
||||
|
||||
public start(): TriggersAndActionsUIPublicPluginStart {
|
||||
public start(_: CoreStart, plugins: PluginsStart): TriggersAndActionsUIPublicPluginStart {
|
||||
import('./application/sections/alerts_table/configuration').then(
|
||||
({ getAlertsTableConfiguration }) => {
|
||||
this.alertsTableConfigurationRegistry.register(
|
||||
getAlertsTableConfiguration(plugins.fieldFormats)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry,
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
"@kbn/expressions-plugin",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/serverless",
|
||||
"@kbn/field-formats-plugin",
|
||||
"@kbn/triggers-actions-ui-types"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue