diff --git a/src/plugins/discover/public/__mocks__/data_view.ts b/src/plugins/discover/public/__mocks__/data_view.ts index d660eef2b9b38..b6f86109b580b 100644 --- a/src/plugins/discover/public/__mocks__/data_view.ts +++ b/src/plugins/discover/public/__mocks__/data_view.ts @@ -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, +}); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx index bd6529dd44314..76694ad53ec17 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx @@ -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(), ], }) ); diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 3d4acfc34d925..e05f4c9d704fa 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -456,23 +456,15 @@ export const DiscoverGrid = ({ const cellActionsFields = useMemo( () => 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] ); diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts index 96edd34596706..dd66cf80eb8b5 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts @@ -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; diff --git a/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx b/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx index dde25228c7e82..d99dc31a8b655 100644 --- a/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/data_table.stories.tsx @@ -84,6 +84,7 @@ export const DataTable = () => { undefined} data={mockTimelineData} id={TableId.test} renderCellValue={StoryCellRenderer} diff --git a/x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx b/x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx index 42523385b5ace..d543cca634c4a 100644 --- a/x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/index.test.tsx @@ -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( + timestampFieldSpec.name === name ? timestampFieldSpec : undefined + } data={data} /> @@ -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', diff --git a/x-pack/packages/security-solution/data_table/components/data_table/index.tsx b/x-pack/packages/security-solution/data_table/components/data_table/index.tsx index 6c71d796dc9ea..0018702ce5e0e 100644 --- a/x-pack/packages/security-solution/data_table/components/data_table/index.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/index.tsx @@ -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( rowHeightsOptions, isEventRenderedView = false, getFieldBrowser, + getFieldSpec, cellActionsTriggerId, ...otherProps }) => { @@ -331,21 +334,20 @@ export const DataTableComponent = React.memo( ); const cellActionsMetadata = useMemo(() => ({ scopeId: id }), [id]); - const cellActionsFields = useMemo( () => 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( diff --git a/x-pack/packages/security-solution/data_table/tsconfig.json b/x-pack/packages/security-solution/data_table/tsconfig.json index da4167b70ade2..473470dab1083 100644 --- a/x-pack/packages/security-solution/data_table/tsconfig.json +++ b/x-pack/packages/security-solution/data_table/tsconfig.json @@ -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" ] } diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index b145fa81345a3..7c1c88360a2e7 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -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(null); @@ -602,6 +605,7 @@ const StatefulEventsViewerComponent: React.FC diff --git a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx index e945e2c4828f2..309db4eb6a409 100644 --- a/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/hooks/trigger_actions_alert_table/use_cell_actions.tsx @@ -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 | 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( (fieldName, rowIndex) => {