mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solutions] Update Events/alerts table to use FieldSpec for CellActions (#161361)
EPIC: https://github.com/elastic/kibana/issues/144943 ## Summary Update Events/alerts table to provide `CellActions` with a complete `FieldSpec`object from DataView ### Affected pages: * Alerts page * Security Dashboards * Rule preview * Host events * Users events ### How to test it Use CellActions on one of the affected pages. ### Checklist - [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ff6099eb3f
commit
6db79db1e0
10 changed files with 67 additions and 71 deletions
|
@ -6,9 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
|
||||
export const fields = [
|
||||
export const shallowMockedFields = [
|
||||
{
|
||||
name: '_source',
|
||||
type: '_source',
|
||||
|
@ -73,6 +73,10 @@ export const fields = [
|
|||
},
|
||||
] as DataView['fields'];
|
||||
|
||||
export const deepMockedFields = shallowMockedFields.map(
|
||||
(field) => new DataViewField(field)
|
||||
) as DataView['fields'];
|
||||
|
||||
export const buildDataViewMock = ({
|
||||
name,
|
||||
fields: definedFields,
|
||||
|
@ -120,4 +124,7 @@ export const buildDataViewMock = ({
|
|||
return dataView;
|
||||
};
|
||||
|
||||
export const dataViewMock = buildDataViewMock({ name: 'the-data-view', fields });
|
||||
export const dataViewMock = buildDataViewMock({
|
||||
name: 'the-data-view',
|
||||
fields: shallowMockedFields,
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import { EuiCopy } from '@elastic/eui';
|
|||
import { act } from 'react-dom/test-utils';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { esHits } from '../../__mocks__/es_hits';
|
||||
import { buildDataViewMock, fields } from '../../__mocks__/data_view';
|
||||
import { buildDataViewMock, deepMockedFields } from '../../__mocks__/data_view';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { DiscoverGrid, DiscoverGridProps } from './discover_grid';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -28,7 +28,7 @@ jest.mock('@kbn/cell-actions', () => ({
|
|||
|
||||
export const dataViewMock = buildDataViewMock({
|
||||
name: 'the-data-view',
|
||||
fields,
|
||||
fields: deepMockedFields,
|
||||
timeFieldName: '@timestamp',
|
||||
});
|
||||
|
||||
|
@ -259,18 +259,8 @@ describe('DiscoverGrid', () => {
|
|||
triggerId: 'test',
|
||||
getCellValue: expect.any(Function),
|
||||
fields: [
|
||||
{
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
searchable: undefined,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
aggregatable: false,
|
||||
searchable: undefined,
|
||||
},
|
||||
dataViewMock.getFieldByName('@timestamp')?.toSpec(),
|
||||
dataViewMock.getFieldByName('message')?.toSpec(),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
|
|
@ -456,23 +456,15 @@ export const DiscoverGrid = ({
|
|||
const cellActionsFields = useMemo<UseDataGridColumnsCellActionsProps['fields']>(
|
||||
() =>
|
||||
cellActionsTriggerId && !isPlainRecord
|
||||
? visibleColumns.map((columnName) => {
|
||||
const field = dataView.getFieldByName(columnName);
|
||||
if (!field) {
|
||||
return {
|
||||
? visibleColumns.map(
|
||||
(columnName) =>
|
||||
dataView.getFieldByName(columnName)?.toSpec() ?? {
|
||||
name: '',
|
||||
type: '',
|
||||
aggregatable: false,
|
||||
searchable: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: columnName,
|
||||
type: field.type,
|
||||
aggregatable: field.aggregatable,
|
||||
searchable: field.searchable,
|
||||
};
|
||||
})
|
||||
}
|
||||
)
|
||||
: undefined,
|
||||
[cellActionsTriggerId, isPlainRecord, visibleColumns, dataView]
|
||||
);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_ma
|
|||
import { SearchInput } from '..';
|
||||
import { getSavedSearchUrl } from '@kbn/saved-search-plugin/public';
|
||||
import { DiscoverServices } from '../build_services';
|
||||
import { dataViewMock } from '../__mocks__/data_view';
|
||||
import { discoverServiceMock } from '../__mocks__/services';
|
||||
import { SavedSearchEmbeddable, SearchEmbeddableConfig } from './saved_search_embeddable';
|
||||
import { render } from 'react-dom';
|
||||
|
@ -23,6 +22,7 @@ import { SHOW_FIELD_STATISTICS } from '../../common';
|
|||
import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import { SavedSearchEmbeddableComponent } from './saved_search_embeddable_component';
|
||||
import { VIEW_MODE } from '../../common/constants';
|
||||
import { buildDataViewMock, deepMockedFields } from '../__mocks__/data_view';
|
||||
|
||||
let discoverComponent: ReactWrapper;
|
||||
|
||||
|
@ -48,6 +48,8 @@ function getSearchResponse(nrOfHits: number) {
|
|||
});
|
||||
}
|
||||
|
||||
const dataViewMock = buildDataViewMock({ name: 'the-data-view', fields: deepMockedFields });
|
||||
|
||||
describe('saved search embeddable', () => {
|
||||
let mountpoint: HTMLDivElement;
|
||||
let filterManagerMock: jest.Mocked<FilterManager>;
|
||||
|
|
|
@ -84,6 +84,7 @@ export const DataTable = () => {
|
|||
<StoryProviders>
|
||||
<DataTableComponent
|
||||
browserFields={{}}
|
||||
getFieldSpec={() => undefined}
|
||||
data={mockTimelineData}
|
||||
id={TableId.test}
|
||||
renderCellValue={StoryCellRenderer}
|
||||
|
|
|
@ -71,6 +71,7 @@ describe('DataTable', () => {
|
|||
const mount = useMountAppended();
|
||||
const props: DataTableProps = {
|
||||
browserFields: mockBrowserFields,
|
||||
getFieldSpec: () => undefined,
|
||||
data: mockTimelineData,
|
||||
id: TableId.test,
|
||||
loadPage: jest.fn(),
|
||||
|
@ -158,11 +159,21 @@ describe('DataTable', () => {
|
|||
describe('cellActions', () => {
|
||||
test('calls useDataGridColumnsCellActions properly', () => {
|
||||
const data = mockTimelineData.slice(0, 1);
|
||||
const timestampFieldSpec = {
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
esTypes: ['date'],
|
||||
searchable: true,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<DataTableComponent
|
||||
cellActionsTriggerId="mockCellActionsTrigger"
|
||||
{...props}
|
||||
getFieldSpec={(name) =>
|
||||
timestampFieldSpec.name === name ? timestampFieldSpec : undefined
|
||||
}
|
||||
data={data}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -171,16 +182,7 @@ describe('DataTable', () => {
|
|||
|
||||
expect(mockUseDataGridColumnsCellActions).toHaveBeenCalledWith({
|
||||
triggerId: 'mockCellActionsTrigger',
|
||||
fields: [
|
||||
{
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
aggregatable: true,
|
||||
esTypes: ['date'],
|
||||
searchable: true,
|
||||
subType: undefined,
|
||||
},
|
||||
],
|
||||
fields: [timestampFieldSpec],
|
||||
getCellValue: expect.any(Function),
|
||||
metadata: {
|
||||
scopeId: 'table-test',
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
useDataGridColumnsCellActions,
|
||||
UseDataGridColumnsCellActionsProps,
|
||||
} from '@kbn/cell-actions';
|
||||
import { FieldSpec } from '@kbn/data-views-plugin/common';
|
||||
import { DataTableModel, DataTableState } from '../../store/data_table/types';
|
||||
|
||||
import { getColumnHeader, getColumnHeaders } from './column_headers/helpers';
|
||||
|
@ -96,6 +97,7 @@ interface BaseDataTableProps {
|
|||
rowHeightsOptions?: EuiDataGridRowHeightsOptions;
|
||||
isEventRenderedView?: boolean;
|
||||
getFieldBrowser: GetFieldBrowser;
|
||||
getFieldSpec: (fieldName: string) => FieldSpec | undefined;
|
||||
cellActionsTriggerId?: string;
|
||||
}
|
||||
|
||||
|
@ -154,6 +156,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
|
|||
rowHeightsOptions,
|
||||
isEventRenderedView = false,
|
||||
getFieldBrowser,
|
||||
getFieldSpec,
|
||||
cellActionsTriggerId,
|
||||
...otherProps
|
||||
}) => {
|
||||
|
@ -331,21 +334,20 @@ export const DataTableComponent = React.memo<DataTableProps>(
|
|||
);
|
||||
|
||||
const cellActionsMetadata = useMemo(() => ({ scopeId: id }), [id]);
|
||||
|
||||
const cellActionsFields = useMemo<UseDataGridColumnsCellActionsProps['fields']>(
|
||||
() =>
|
||||
cellActionsTriggerId
|
||||
? // TODO use FieldSpec object instead of column
|
||||
columnHeaders.map((column) => ({
|
||||
name: column.id,
|
||||
type: column.type ?? '', // When type is an empty string all cell actions are incompatible
|
||||
aggregatable: column.aggregatable ?? false,
|
||||
searchable: column.searchable ?? false,
|
||||
esTypes: column.esTypes ?? [],
|
||||
subType: column.subType,
|
||||
}))
|
||||
? columnHeaders.map(
|
||||
(column) =>
|
||||
getFieldSpec(column.id) ?? {
|
||||
name: column.id,
|
||||
type: '', // When type is an empty string all cell actions are incompatible
|
||||
aggregatable: false,
|
||||
searchable: false,
|
||||
}
|
||||
)
|
||||
: undefined,
|
||||
[cellActionsTriggerId, columnHeaders]
|
||||
[cellActionsTriggerId, columnHeaders, getFieldSpec]
|
||||
);
|
||||
|
||||
const getCellValue = useCallback<UseDataGridColumnsCellActionsProps['getCellValue']>(
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/ui-actions-plugin"
|
||||
"@kbn/ui-actions-plugin",
|
||||
"@kbn/data-views-plugin"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ import { useAlertBulkActions } from './use_alert_bulk_actions';
|
|||
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
|
||||
import { StatefulEventContext } from './stateful_event_context';
|
||||
import { defaultUnit } from '../toolbar/unit';
|
||||
import { useGetFieldSpec } from '../../hooks/use_get_field_spec';
|
||||
|
||||
const storage = new Storage(localStorage);
|
||||
|
||||
|
@ -184,6 +185,8 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
loading: isLoadingIndexPattern,
|
||||
} = useSourcererDataView(sourcererScope);
|
||||
|
||||
const getFieldSpec = useGetFieldSpec(sourcererScope);
|
||||
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
|
||||
const editorActionsRef = useRef<FieldEditorActions>(null);
|
||||
|
@ -602,6 +605,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
isEventRenderedView={tableView === 'eventRenderedView'}
|
||||
rowHeightsOptions={rowHeightsOptions}
|
||||
getFieldBrowser={getFieldBrowser}
|
||||
getFieldSpec={getFieldSpec}
|
||||
/>
|
||||
</StatefulEventContext.Provider>
|
||||
</ScrollableFlexItem>
|
||||
|
|
|
@ -5,18 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { BrowserField, TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import type { AlertsTableConfigurationRegistry } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { TableId, tableDefaults, dataTableSelectors } from '@kbn/securitysolution-data-table';
|
||||
import { getAllFieldsByName } from '../../../common/containers/source';
|
||||
import type { UseDataGridColumnsSecurityCellActionsProps } from '../../../common/components/cell_actions';
|
||||
import { useDataGridColumnsSecurityCellActions } from '../../../common/components/cell_actions';
|
||||
import { SecurityCellActionsTrigger, SecurityCellActionType } from '../../../actions/constants';
|
||||
import { VIEW_SELECTION } from '../../../../common/constants';
|
||||
import { useSourcererDataView } from '../../../common/containers/sourcerer';
|
||||
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||
import { useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useGetFieldSpec } from '../../../common/hooks/use_get_field_spec';
|
||||
|
||||
export const getUseCellActionsHook = (tableId: TableId) => {
|
||||
const useCellActions: AlertsTableConfigurationRegistry['useCellActions'] = ({
|
||||
|
@ -24,7 +23,7 @@ export const getUseCellActionsHook = (tableId: TableId) => {
|
|||
data,
|
||||
dataGridRef,
|
||||
}) => {
|
||||
const { browserFields } = useSourcererDataView(SourcererScopeName.detections);
|
||||
const getFieldSpec = useGetFieldSpec(SourcererScopeName.detections);
|
||||
/**
|
||||
* There is difference between how `triggers actions` fetched data v/s
|
||||
* how security solution fetches data via timelineSearchStrategy
|
||||
|
@ -35,7 +34,6 @@ export const getUseCellActionsHook = (tableId: TableId) => {
|
|||
*
|
||||
*/
|
||||
|
||||
const browserFieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]);
|
||||
const finalData = useMemo(
|
||||
() =>
|
||||
(data as TimelineNonEcsData[][]).map((row) =>
|
||||
|
@ -66,19 +64,16 @@ export const getUseCellActionsHook = (tableId: TableId) => {
|
|||
if (viewMode === VIEW_SELECTION.eventRenderedView) {
|
||||
return undefined;
|
||||
}
|
||||
return columns.map((column) => {
|
||||
// TODO use FieldSpec object instead of browserField
|
||||
const browserField: Partial<BrowserField> | undefined = browserFieldsByName[column.id];
|
||||
return {
|
||||
name: column.id,
|
||||
type: browserField?.type ?? '', // When type is an empty string all cell actions are incompatible
|
||||
esTypes: browserField?.esTypes ?? [],
|
||||
aggregatable: browserField?.aggregatable ?? false,
|
||||
searchable: browserField?.searchable ?? false,
|
||||
subType: browserField?.subType,
|
||||
};
|
||||
});
|
||||
}, [browserFieldsByName, columns, viewMode]);
|
||||
return columns.map(
|
||||
(column) =>
|
||||
getFieldSpec(column.id) ?? {
|
||||
name: '',
|
||||
type: '', // When type is an empty string all cell actions are incompatible
|
||||
aggregatable: false,
|
||||
searchable: false,
|
||||
}
|
||||
);
|
||||
}, [getFieldSpec, columns, viewMode]);
|
||||
|
||||
const getCellValue = useCallback<UseDataGridColumnsSecurityCellActionsProps['getCellValue']>(
|
||||
(fieldName, rowIndex) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue