x-pack/platform/packages/shared/response-ops/alerts-table/README.md
An abstraction on top of EuiDataGrid dedicated to rendering alert documents.
In addition to EuiDataGrid's functionality, the table manages the paginated and cached fetching of alerts, based on
the provided ruleTypeIds and consumers (the final query can be refined through the query, sort, minScore
props).
The id prop is used to persist the table state in localStorage.
<AlertsTable
id="my-alerts-table"
ruleTypeIds={ruleTypeIds}
consumers={consumers}
query={esQuery}
sort={defaultAlertsTableSort}
renderCellValue={renderCellValue}
renderActionsCell={AlertActionsCell}
services={{ ... }}
/>
Just like in EuiDataGrid, the columns are customizable through the columns prop. In addition to those, the table
renders an "Actions" column with default alert call-to-actions and provides row selection and bulk actions
functionality.
// The @kbn/rule-data-utils package exports constants
// for many common alert field keys
import { ALERT_RULE_NAME } from '@kbn/rule-data-utils';
<AlertsTable columns={[
{
displayAsText: 'Rule',
id: ALERT_RULE_NAME,
initialWidth: 230,
},
]} />
All the sub-components of the table are customizable through the render* props (renderCellValue,
renderCellPopover, renderActionsCell, etc.). Values passed to these props are treated as components, allowing hooks,
context, and other React concepts to be used.
const CustomCellValue: GetAlertsTableProp<'renderCellValue'> = ({ alert }) => {
// ...
};
<AlertsTable renderCellValue={CustomCellValue} />
All the sub-component renderers receive as part of their props a context object (see EuiDataGrid's
cellContext)
with common utilities and services (i.e. the fetched alerts, loading states etc.).
You can add properties to this context by means of the additionalContext prop:
<AlertsTable
additionalContext={{
myCustomProperty: 'my custom value',
}}
renderCellValue={({ myCustomProperty /*string*/ }) => {
// ...
}}
/>
The context type is inferred based on the additionalContext prop and all render functions props are typed accordingly.
To reduce reliance on a large number of props, you can use the useAlertsTableContext hook to access the same context
in any sub-component.
const CustomCellValue = ({ alert }) => {
const { alertsCount, myCustomProperty } = useAlertsTableContext<MyAdditionalContext>();
// ...
};
In order to define your custom sub-components separately from the table but still benefit from the context type
inference, you may want to extract props from the AlertsTableProps type. The GetAlertsTableProp utility type is
provided for this: it extracts the type of a specific prop from the AlertsTableProps type, excluding undefined in
case of optional props.
import type { GetAlertsTableProp } from '@kbn/response-ops-alerts-table/types';
export const CustomCellValue: GetAlertsTableProp<'renderCellValue'> = ({ alert }) => {
// ...
};
If you also have an additional context, you can define a type for it and wrap the AlertsTableProps, providing it as a
generic:
import type { AlertsTableProps } from '@kbn/response-ops-alerts-table/types';
interface MyAdditionalContext {
myCustomProperty: string;
}
export type MyAlertsTableProps = AlertsTableProps<MyAdditionalContext>;
export type GetMyAlertsTableProp<K extends keyof MyAdditionalContext> = Exclude<MyAlertsTableProps[K], undefined>;
export const CustomCellValue: GetMyAlertsTableProp<'renderCellValue'> = ({ myCustomProperty }) => {
// ...
};
<AlertsTable<MyAdditionalContext>
additionalContext={{
myCustomProperty: 'my custom value',
}}
renderCellValue={CustomCellValue}
/>
The table supports opening rows in a detailed view. By default, this is rendered as a flyout and is triggered through
the "View alert details" row action. You can take control of which row is expanded by setting the expandedAlertIndex
and onExpandedAlertIndexChange props:
const [expandedAlertIndex, setExpandedAlertIndex] = useState<number | null>(null);
<AlertsTable
expandedAlertIndex={expandedAlertIndex}
onExpandedAlertIndexChange={setExpandedAlertIndex}
/>
expandedAlertIndex is a zero-based index that refers to the position of the alert in overall results, not just the
current page (so it goes from 0 to alertsCount - 1). This is to facilitate the implementation of pagination
controls that can change the expanded alert independently of the current page. The table will automatically switch to
a new page when crossing page boundaries using this navigation method.
Moreover, you can customize the expanded alert view through the renderExpandedAlertView prop:
const CustomExpandedAlertView: GetAlertsTableProp<'renderExpandedAlertView'> = ({
// Like any other renderer, this receives the render context as part of its props
pageSize,
pageIndex,
expandedAlertIndex,
onExpandedAlertIndexChange,
isLoading,
alerts,
}) => {
// `alerts` contains only the alerts of the current page, so we need to calculate
// the index of the expanded alert in this array
const alertIndexInPage = expandedAlertIndex - pageIndex * pageSize;
// This can be undefined when a new page of alerts is still loading.
// Use this in combination with isLoading to show a loading indicator
const alert = alerts[alertIndexInPage] as Alert | undefined;
// To implement pagination controls, use `onExpandedAlertIndexChange` to change
// the expanded alert index
render (...);
};
<AlertsTable renderExpandedAlertView={CustomExpandedAlertView} />
The table relies on the following Kibana services, expected in the services prop:
datahttpnotificationsfieldFormatsapplicationlicensingsettingscases (optional)The table has built-in integration with Maintenance Windows and Cases. If alerts have maintenance windows or cases
associated to them, they will be loaded and made available through the maintenanceWindows and cases properties of
the render context.
A special cell renderer is used by default for the kibana.alert.maintenance_window_ids and kibana.alert.case_ids
columns.
Contrary to the previous implementation exported by triggersActionsUI, this package doesn't prescribe how to lazy load
the table component; a default export is just provided for convenience. However, do consider that
the React.lazy function loses the original generic types of the component. To make the type inference work correctly,
you can assert the type of the lazy loaded component using a type import:
import type { AlertsTable as AlertsTableType } from '@kbn/response-ops-alerts-table';
const AlertsTable = React.lazy(() => import('@kbn/response-ops-alerts-table')) as AlertsTableType;
When mocking the table, keep in mind that the component is manually typed as a normal function component (to keep its generic types), but it's actually a memoized, forwardRef'ed component. To mock it properly, mock the entire module:
jest.mock('@kbn/response-ops-alerts-table', () => ({
AlertsTable: jest.fn().mockImplementation(() => <div data-test-subj="alerts-table" />),
}));
triggersActionsUI?render*).EuiDataGrid props are exposed for customization.