mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[RAC] [TGrid] Implements cell actions in the TGrid (#107771)
## Summary This PR implements cell actions in the `TGrid`, rendering them via `EuiDataGrid`, per the `Before` and `After` screenshots below: ### Before Users previously hovered over a draggable field to view and trigger cell actions: <img width="1348" alt="legacy_cell_actions" src="https://user-images.githubusercontent.com/4459398/128351498-49b4d224-6c51-4293-b14f-46bbb58f7cb3.png"> _Above: legacy `TGrid` cell action rendering_ ### After Cell actions are now rendered via `EuiDataGrid` cell actions: <img width="997" alt="euidatagrid_cell_actions" src="https://user-images.githubusercontent.com/4459398/128358847-c5540ea4-8ba1-4b35-ab6b-3b3e39ae54ce.png"> _Above: new `TGrid` cell action rendering via `EuiDataGrid`_ ## Technical Details Every instance of the `TGrid` on a page can specify its own set of cell actions via `defaultCellActions` when calling the `timelines.getTGrid()` function to create an instance. For example, the Observability Alerts `TGrid` is initialized in with a default set of actions in `x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx`, as shown in the code below: ```ts {timelines.getTGrid<'standalone'>({ type: 'standalone', columns, deletedEventIds: [], defaultCellActions: getDefaultCellActions({ enableFilterActions: false }), // <-- defaultCellActions // ... </> ``` The type of the `defaultCellActions` is: ```ts defaultCellActions?: TGridCellAction[]; ``` and the definition of `TGridCellAction` is in `x-pack/plugins/timelines/common/types/timeline/columns/index.tsx`: ```ts /** * A `TGridCellAction` function accepts `data`, where each row of data is * represented as a `TimelineNonEcsData[]`. For example, `data[0]` would * contain a `TimelineNonEcsData[]` with the first row of data. * * A `TGridCellAction` returns a function that has access to all the * `EuiDataGridColumnCellActionProps`, _plus_ access to `data`, * which enables code like the following example to be written: * * Example: * ``` * ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { * const value = getMappedNonEcsValue({ * data: data[rowIndex], // access a specific row's values * fieldName: columnId, * }); * * return ( * <Component onClick={() => alert(`row ${rowIndex} col ${columnId} has value ${value}`)} iconType="heart"> * {'Love it'} * </Component> * ); * }; * ``` */ export type TGridCellAction = ({ browserFields, data, }: { browserFields: BrowserFields; /** each row of data is represented as one TimelineNonEcsData[] */ data: TimelineNonEcsData[][]; }) => (props: EuiDataGridColumnCellActionProps) => ReactNode; ``` For example, the following `TGridCellAction[]` defines the `Copy to clipboard` action for the Observability Alerts table in `x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx`: ```ts /** actions common to all cells (e.g. copy to clipboard) */ const commonCellActions: TGridCellAction[] = [ ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => { const { timelines } = useKibanaServices(); const value = getMappedNonEcsValue({ data: data[rowIndex], fieldName: columnId, }); return ( <> {timelines.getHoverActions().getCopyButton({ Component, field: columnId, isHoverAction: false, ownFocus: false, showTooltip: false, value, })} </> ); }, ]; ``` Note that an _implementation_ of the copy to clipboard cell action, including the button, is available for both the Observability and Security solutions to use via `timelines.getHoverActions().getCopyButton()`, (and both solutions use it in this PR), but there's no requirement to use that specific implementation of the copy action. ### Security Solution cell actions All previously-available hover actions in the Security Solution are now available as cell actions, i.e.: - Filter for value - Filter out value - Add to timeline investigation - Show Top `<field>` (only enabled for some data types) - Copy to clipboard ### Observability cell actions In this PR: - Only the `Copy to clipboard` cell action is enabled by default in the Observability Alerts table - The `Filter for value` and `Filter out value` cell actions may be enabled in the `Observability` solution by changing a single line of code, (setting `enableFilterActions` to true), on the following line in `x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx`: ```js defaultCellActions: getDefaultCellActions({ enableFilterActions: false }), // <-- set this to `true` to enable the filter actions ``` `enableFilterActions` is set to `false` in this PR because the Observability Alerts page's search bar, defined in `x-pack/plugins/observability/public/pages/alerts/alerts_search_bar.tsx`: ```ts return ( <SearchBar indexPatterns={dynamicIndexPattern} placeholder={i18n.translate('xpack.observability.alerts.searchBarPlaceholder', { defaultMessage: 'kibana.alert.evaluation.threshold > 75', })} query={{ query: query ?? '', language: queryLanguage }} // ... /> ```` must be integrated with a `filterManager` to display the filters. A `filterManager` instance may be obtained in the Observability solution via the following boilerplate: ```ts const { services: { data: { query: { filterManager }, }, }, } = useKibana<ObservabilityPublicPluginsStart>(); ``` ## Desk testing To desk test this PR, you must enable feature flags in the Observability and Security Solution: - To desk test the `Observability > Alerts` page, add the following settings to `config/kibana.dev.yml`: ``` xpack.observability.unsafe.cases.enabled: true xpack.observability.unsafe.alertingExperience.enabled: true xpack.ruleRegistry.write.enabled: true ``` - To desk test the TGrid in the following Security Solution, edit `x-pack/plugins/security_solution/common/experimental_features.ts` and in the `allowedExperimentalValues` section set: ```typescript tGridEnabled: true, ``` cc @mdefazio
This commit is contained in:
parent
764388e713
commit
5f409bc339
21 changed files with 556 additions and 82 deletions
|
@ -5,8 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui';
|
||||
import { IFieldSubType } from '../../../../../../../src/plugins/data/common';
|
||||
import { BrowserFields } from '../../../search_strategy/index_fields';
|
||||
import { TimelineNonEcsData } from '../../../search_strategy/timeline';
|
||||
|
||||
export type ColumnHeaderType = 'not-filtered' | 'text-filter';
|
||||
|
@ -14,6 +17,40 @@ export type ColumnHeaderType = 'not-filtered' | 'text-filter';
|
|||
/** Uniquely identifies a column */
|
||||
export type ColumnId = string;
|
||||
|
||||
/**
|
||||
* A `TGridCellAction` function accepts `data`, where each row of data is
|
||||
* represented as a `TimelineNonEcsData[]`. For example, `data[0]` would
|
||||
* contain a `TimelineNonEcsData[]` with the first row of data.
|
||||
*
|
||||
* A `TGridCellAction` returns a function that has access to all the
|
||||
* `EuiDataGridColumnCellActionProps`, _plus_ access to `data`,
|
||||
* which enables code like the following example to be written:
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* ({ data }: { data: TimelineNonEcsData[][] }) => ({ rowIndex, columnId, Component }) => {
|
||||
* const value = getMappedNonEcsValue({
|
||||
* data: data[rowIndex], // access a specific row's values
|
||||
* fieldName: columnId,
|
||||
* });
|
||||
*
|
||||
* return (
|
||||
* <Component onClick={() => alert(`row ${rowIndex} col ${columnId} has value ${value}`)} iconType="heart">
|
||||
* {'Love it'}
|
||||
* </Component>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export type TGridCellAction = ({
|
||||
browserFields,
|
||||
data,
|
||||
}: {
|
||||
browserFields: BrowserFields;
|
||||
/** each row of data is represented as one TimelineNonEcsData[] */
|
||||
data: TimelineNonEcsData[][];
|
||||
}) => (props: EuiDataGridColumnCellActionProps) => ReactNode;
|
||||
|
||||
/** The specification of a column header */
|
||||
export type ColumnHeaderOptions = Pick<
|
||||
EuiDataGridColumn,
|
||||
|
@ -26,6 +63,7 @@ export type ColumnHeaderOptions = Pick<
|
|||
| 'isSortable'
|
||||
> & {
|
||||
aggregatable?: boolean;
|
||||
tGridCellActions?: TGridCellAction[];
|
||||
category?: string;
|
||||
columnHeaderType: ColumnHeaderType;
|
||||
description?: string;
|
|
@ -25,3 +25,9 @@ export const COPY_TO_THE_CLIPBOARD = i18n.translate(
|
|||
defaultMessage: 'Copy to the clipboard',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUCCESS_TOAST_TITLE = (field: string) =>
|
||||
i18n.translate('xpack.timelines.clipboard.copy.successToastTitle', {
|
||||
values: { field },
|
||||
defaultMessage: 'Copied field {field} to the clipboard',
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { DraggableId } from 'react-beautiful-dnd';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
@ -44,12 +44,15 @@ const useGetHandleStartDragToTimeline = ({
|
|||
};
|
||||
|
||||
export interface AddToTimelineButtonProps extends HoverActionComponentProps {
|
||||
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
|
||||
Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
|
||||
draggableId?: DraggableId;
|
||||
dataProvider?: DataProvider[] | DataProvider;
|
||||
}
|
||||
|
||||
const AddToTimelineButton: React.FC<AddToTimelineButtonProps> = React.memo(
|
||||
({
|
||||
Component,
|
||||
closePopOver,
|
||||
dataProvider,
|
||||
defaultFocusedButtonRef,
|
||||
|
@ -96,6 +99,33 @@ const AddToTimelineButton: React.FC<AddToTimelineButtonProps> = React.memo(
|
|||
}
|
||||
}, [handleStartDragToTimeline, keyboardEvent, ownFocus]);
|
||||
|
||||
const button = useMemo(
|
||||
() =>
|
||||
Component ? (
|
||||
<Component
|
||||
aria-label={i18n.ADD_TO_TIMELINE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
data-test-subj="add-to-timeline"
|
||||
iconType="timeline"
|
||||
onClick={handleStartDragToTimeline}
|
||||
title={i18n.ADD_TO_TIMELINE}
|
||||
>
|
||||
{i18n.ADD_TO_TIMELINE}
|
||||
</Component>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.ADD_TO_TIMELINE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="add-to-timeline"
|
||||
iconSize="s"
|
||||
iconType="timeline"
|
||||
onClick={handleStartDragToTimeline}
|
||||
/>
|
||||
),
|
||||
[Component, defaultFocusedButtonRef, handleStartDragToTimeline]
|
||||
);
|
||||
|
||||
return showTooltip ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
|
@ -110,26 +140,10 @@ const AddToTimelineButton: React.FC<AddToTimelineButtonProps> = React.memo(
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.ADD_TO_TIMELINE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="add-to-timeline"
|
||||
iconSize="s"
|
||||
iconType="timeline"
|
||||
onClick={handleStartDragToTimeline}
|
||||
/>
|
||||
{button}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.ADD_TO_TIMELINE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="add-to-timeline"
|
||||
iconSize="s"
|
||||
iconType="timeline"
|
||||
onClick={handleStartDragToTimeline}
|
||||
/>
|
||||
button
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { stopPropagationAndPreventDefault } from '../../../../common';
|
||||
import { WithCopyToClipboard } from '../../clipboard/with_copy_to_clipboard';
|
||||
import { HoverActionComponentProps } from './types';
|
||||
import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../clipboard';
|
||||
import { useAppToasts } from '../../../hooks/use_app_toasts';
|
||||
import { COPY_TO_CLIPBOARD } from '../../t_grid/body/translations';
|
||||
import { SUCCESS_TOAST_TITLE } from '../../clipboard/translations';
|
||||
|
||||
export const FIELD = i18n.translate('xpack.timelines.hoverActions.fieldLabel', {
|
||||
defaultMessage: 'Field',
|
||||
|
@ -19,11 +25,14 @@ export const FIELD = i18n.translate('xpack.timelines.hoverActions.fieldLabel', {
|
|||
export const COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT = 'c';
|
||||
|
||||
export interface CopyProps extends HoverActionComponentProps {
|
||||
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
|
||||
Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
|
||||
isHoverAction?: boolean;
|
||||
}
|
||||
|
||||
const CopyButton: React.FC<CopyProps> = React.memo(
|
||||
({ closePopOver, field, isHoverAction, keyboardEvent, ownFocus, value }) => {
|
||||
({ Component, closePopOver, field, isHoverAction, keyboardEvent, ownFocus, value }) => {
|
||||
const { addSuccess } = useAppToasts();
|
||||
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (!ownFocus) {
|
||||
|
@ -42,13 +51,34 @@ const CopyButton: React.FC<CopyProps> = React.memo(
|
|||
}
|
||||
}
|
||||
}, [closePopOver, keyboardEvent, ownFocus]);
|
||||
return (
|
||||
|
||||
const text = useMemo(() => `${field}${value != null ? `: "${value}"` : ''}`, [field, value]);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
const isSuccess = copy(text, { debug: true });
|
||||
|
||||
if (isSuccess) {
|
||||
addSuccess(SUCCESS_TOAST_TITLE(field), { toastLifeTimeMs: 800 });
|
||||
}
|
||||
}, [addSuccess, field, text]);
|
||||
|
||||
return Component ? (
|
||||
<Component
|
||||
aria-label={COPY_TO_CLIPBOARD}
|
||||
data-test-subj="copy-to-clipboard"
|
||||
iconType="copyClipboard"
|
||||
onClick={onClick}
|
||||
title={COPY_TO_CLIPBOARD}
|
||||
>
|
||||
{COPY_TO_CLIPBOARD}
|
||||
</Component>
|
||||
) : (
|
||||
<div ref={panelRef}>
|
||||
<WithCopyToClipboard
|
||||
data-test-subj="copy-to-clipboard"
|
||||
isHoverAction={isHoverAction}
|
||||
keyboardShortcut={ownFocus ? COPY_TO_CLIPBOARD_KEYBOARD_SHORTCUT : ''}
|
||||
text={`${field}${value != null ? `: "${value}"` : ''}`}
|
||||
text={text}
|
||||
titleSummary={FIELD}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
|
@ -23,6 +23,7 @@ export type FilterForValueProps = HoverActionComponentProps & FilterValueFnArgs;
|
|||
|
||||
const FilterForValueButton: React.FC<FilterForValueProps> = React.memo(
|
||||
({
|
||||
Component,
|
||||
closePopOver,
|
||||
defaultFocusedButtonRef,
|
||||
field,
|
||||
|
@ -63,6 +64,33 @@ const FilterForValueButton: React.FC<FilterForValueProps> = React.memo(
|
|||
}
|
||||
}, [filterForValueFn, keyboardEvent, ownFocus]);
|
||||
|
||||
const button = useMemo(
|
||||
() =>
|
||||
Component ? (
|
||||
<Component
|
||||
aria-label={FILTER_FOR_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
data-test-subj="filter-for-value"
|
||||
iconType="plusInCircle"
|
||||
onClick={filterForValueFn}
|
||||
title={FILTER_FOR_VALUE}
|
||||
>
|
||||
{FILTER_FOR_VALUE}
|
||||
</Component>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_FOR_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-for-value"
|
||||
iconSize="s"
|
||||
iconType="plusInCircle"
|
||||
onClick={filterForValueFn}
|
||||
/>
|
||||
),
|
||||
[Component, defaultFocusedButtonRef, filterForValueFn]
|
||||
);
|
||||
|
||||
return showTooltip ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
|
@ -77,26 +105,10 @@ const FilterForValueButton: React.FC<FilterForValueProps> = React.memo(
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_FOR_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-for-value"
|
||||
iconSize="s"
|
||||
iconType="plusInCircle"
|
||||
onClick={filterForValueFn}
|
||||
/>
|
||||
{button}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_FOR_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-for-value"
|
||||
iconSize="s"
|
||||
iconType="plusInCircle"
|
||||
onClick={filterForValueFn}
|
||||
/>
|
||||
button
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
|
@ -22,6 +22,7 @@ export const FILTER_OUT_VALUE_KEYBOARD_SHORTCUT = 'o';
|
|||
|
||||
const FilterOutValueButton: React.FC<HoverActionComponentProps & FilterValueFnArgs> = React.memo(
|
||||
({
|
||||
Component,
|
||||
closePopOver,
|
||||
defaultFocusedButtonRef,
|
||||
field,
|
||||
|
@ -64,6 +65,33 @@ const FilterOutValueButton: React.FC<HoverActionComponentProps & FilterValueFnAr
|
|||
}
|
||||
}, [filterOutValueFn, keyboardEvent, ownFocus]);
|
||||
|
||||
const button = useMemo(
|
||||
() =>
|
||||
Component ? (
|
||||
<Component
|
||||
aria-label={FILTER_OUT_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
data-test-subj="filter-out-value"
|
||||
iconType="minusInCircle"
|
||||
onClick={filterOutValueFn}
|
||||
title={FILTER_OUT_VALUE}
|
||||
>
|
||||
{FILTER_OUT_VALUE}
|
||||
</Component>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_OUT_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-out-value"
|
||||
iconSize="s"
|
||||
iconType="minusInCircle"
|
||||
onClick={filterOutValueFn}
|
||||
/>
|
||||
),
|
||||
[Component, defaultFocusedButtonRef, filterOutValueFn]
|
||||
);
|
||||
|
||||
return showTooltip ? (
|
||||
<EuiToolTip
|
||||
content={
|
||||
|
@ -78,26 +106,10 @@ const FilterOutValueButton: React.FC<HoverActionComponentProps & FilterValueFnAr
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_OUT_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-out-value"
|
||||
iconSize="s"
|
||||
iconType="minusInCircle"
|
||||
onClick={filterOutValueFn}
|
||||
/>
|
||||
{button}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EuiButtonIcon
|
||||
aria-label={FILTER_OUT_VALUE}
|
||||
buttonRef={defaultFocusedButtonRef}
|
||||
className="timelines__hoverActionButton"
|
||||
data-test-subj="filter-out-value"
|
||||
iconSize="s"
|
||||
iconType="minusInCircle"
|
||||
onClick={filterOutValueFn}
|
||||
/>
|
||||
button
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonIconPropsForButton } from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiButtonIcon, EuiButtonIconPropsForButton } from '@elastic/eui';
|
||||
import { FilterManager } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
export interface FilterValueFnArgs {
|
||||
/** `Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality */
|
||||
Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon;
|
||||
field: string;
|
||||
value: string[] | string | null | undefined;
|
||||
filterManager: FilterManager | undefined;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import {
|
||||
EuiDataGrid,
|
||||
EuiDataGridColumn,
|
||||
EuiDataGridCellValueElementProps,
|
||||
EuiDataGridControlColumn,
|
||||
EuiDataGridStyle,
|
||||
|
@ -27,6 +28,7 @@ import React, {
|
|||
import { connect, ConnectedProps, useDispatch } from 'react-redux';
|
||||
|
||||
import {
|
||||
TGridCellAction,
|
||||
TimelineId,
|
||||
TimelineTabs,
|
||||
BulkActionsProp,
|
||||
|
@ -66,6 +68,7 @@ interface OwnProps {
|
|||
additionalControls?: React.ReactNode;
|
||||
browserFields: BrowserFields;
|
||||
data: TimelineItem[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
id: string;
|
||||
isEventViewer?: boolean;
|
||||
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
|
||||
|
@ -211,6 +214,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
browserFields,
|
||||
columnHeaders,
|
||||
data,
|
||||
defaultCellActions,
|
||||
excludedRowRendererIds,
|
||||
id,
|
||||
isEventViewer = false,
|
||||
|
@ -461,6 +465,24 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
sort,
|
||||
]);
|
||||
|
||||
const columnsWithCellActions: EuiDataGridColumn[] = useMemo(
|
||||
() =>
|
||||
columnHeaders.map((header) => {
|
||||
const buildAction = (tGridCellAction: TGridCellAction) =>
|
||||
tGridCellAction({
|
||||
data: data.map((row) => row.data),
|
||||
browserFields,
|
||||
});
|
||||
|
||||
return {
|
||||
...header,
|
||||
cellActions:
|
||||
header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction),
|
||||
};
|
||||
}),
|
||||
[browserFields, columnHeaders, data, defaultCellActions]
|
||||
);
|
||||
|
||||
const renderTGridCellValue: (x: EuiDataGridCellValueElementProps) => React.ReactNode = ({
|
||||
columnId,
|
||||
rowIndex,
|
||||
|
@ -494,7 +516,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
<EuiDataGrid
|
||||
data-test-subj="body-data-grid"
|
||||
aria-label={i18n.TGRID_BODY_ARIA_LABEL}
|
||||
columns={columnHeaders}
|
||||
columns={columnsWithCellActions}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
gridStyle={gridStyle}
|
||||
leadingControlColumns={leadingTGridControlColumns}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Direction } from '../../../../common/search_strategy';
|
|||
import type { DocValueFields } from '../../../../common/search_strategy';
|
||||
import type { CoreStart } from '../../../../../../../src/core/public';
|
||||
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import { TGridCellAction, TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
@ -104,6 +104,7 @@ export interface TGridIntegratedProps {
|
|||
browserFields: BrowserFields;
|
||||
columns: ColumnHeaderOptions[];
|
||||
dataProviders: DataProvider[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
deletedEventIds: Readonly<string[]>;
|
||||
docValueFields: DocValueFields[];
|
||||
end: string;
|
||||
|
@ -138,6 +139,7 @@ export interface TGridIntegratedProps {
|
|||
const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
|
||||
browserFields,
|
||||
columns,
|
||||
defaultCellActions,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
docValueFields,
|
||||
|
@ -309,6 +311,7 @@ const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
|
|||
activePage={pageInfo.activePage}
|
||||
browserFields={browserFields}
|
||||
data={nonDeletedEvents}
|
||||
defaultCellActions={defaultCellActions}
|
||||
id={id}
|
||||
isEventViewer={true}
|
||||
loadPage={loadPage}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useDispatch } from 'react-redux';
|
|||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { Direction } from '../../../../common/search_strategy';
|
||||
import type { CoreStart } from '../../../../../../../src/core/public';
|
||||
import { TimelineTabs } from '../../../../common/types/timeline';
|
||||
import { TGridCellAction, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import type {
|
||||
CellValueElementProps,
|
||||
ColumnHeaderOptions,
|
||||
|
@ -98,6 +98,7 @@ const HeaderFilterGroupWrapper = styled.header<{ show: boolean }>`
|
|||
|
||||
export interface TGridStandaloneProps {
|
||||
columns: ColumnHeaderOptions[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
deletedEventIds: Readonly<string[]>;
|
||||
end: string;
|
||||
loadingText: React.ReactNode;
|
||||
|
@ -127,6 +128,7 @@ const basicUnit = (n: number) => i18n.UNIT(n);
|
|||
|
||||
const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
||||
columns,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
end,
|
||||
loadingText,
|
||||
|
@ -322,6 +324,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
activePage={pageInfo.activePage}
|
||||
browserFields={browserFields}
|
||||
data={nonDeletedEvents}
|
||||
defaultCellActions={defaultCellActions}
|
||||
id={STANDALONE_ID}
|
||||
isEventViewer={true}
|
||||
loadPage={loadPage}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue