Skip to content

Commit

Permalink
[Security Solutions] Update Events/alerts table to use FieldSpec for …
Browse files Browse the repository at this point in the history
…CellActions (#161361)

EPIC: #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 <[email protected]>
  • Loading branch information
machadoum and kibanamachine authored Jul 10, 2023
1 parent ff6099e commit 6db79db
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 71 deletions.
13 changes: 10 additions & 3 deletions src/plugins/discover/public/__mocks__/data_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -28,7 +28,7 @@ jest.mock('@kbn/cell-actions', () => ({

export const dataViewMock = buildDataViewMock({
name: 'the-data-view',
fields,
fields: deepMockedFields,
timeFieldName: '@timestamp',
});

Expand Down Expand Up @@ -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(),
],
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;

Expand All @@ -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>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const DataTable = () => {
<StoryProviders>
<DataTableComponent
browserFields={{}}
getFieldSpec={() => undefined}
data={mockTimelineData}
id={TableId.test}
renderCellValue={StoryCellRenderer}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('DataTable', () => {
const mount = useMountAppended();
const props: DataTableProps = {
browserFields: mockBrowserFields,
getFieldSpec: () => undefined,
data: mockTimelineData,
id: TableId.test,
loadPage: jest.fn(),
Expand Down Expand Up @@ -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>
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,6 +97,7 @@ interface BaseDataTableProps {
rowHeightsOptions?: EuiDataGridRowHeightsOptions;
isEventRenderedView?: boolean;
getFieldBrowser: GetFieldBrowser;
getFieldSpec: (fieldName: string) => FieldSpec | undefined;
cellActionsTriggerId?: string;
}

Expand Down Expand Up @@ -154,6 +156,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
rowHeightsOptions,
isEventRenderedView = false,
getFieldBrowser,
getFieldSpec,
cellActionsTriggerId,
...otherProps
}) => {
Expand Down Expand Up @@ -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']>(
Expand Down
3 changes: 2 additions & 1 deletion x-pack/packages/security-solution/data_table/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -602,6 +605,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
isEventRenderedView={tableView === 'eventRenderedView'}
rowHeightsOptions={rowHeightsOptions}
getFieldBrowser={getFieldBrowser}
getFieldSpec={getFieldSpec}
/>
</StatefulEventContext.Provider>
</ScrollableFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@
* 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'] = ({
columns,
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
Expand All @@ -35,7 +34,6 @@ export const getUseCellActionsHook = (tableId: TableId) => {
*
*/

const browserFieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]);
const finalData = useMemo(
() =>
(data as TimelineNonEcsData[][]).map((row) =>
Expand Down Expand Up @@ -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) => {
Expand Down

0 comments on commit 6db79db

Please sign in to comment.