[Security Solution] getDataViewStateFromIndexFields was using wrong type as part of a cast (#158594)

## Summary

Fixes an issue with the field browser where all types currently display
as unkown, this was because in a code path where a type cast happens, we
were using the wrong type. To see this, remove the as unknown from the
cast, and the typescript compiler will show the problem:
```
'BrowserField' is deprecated.ts(6385)
index.ts(70, 4): The declaration was marked as deprecated here.
Conversion of type 'DataViewField' to type 'BrowserField' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Type 'DataViewField' is missing the following properties from type 'BrowserField': category, description, example, fields, and 2 more.ts(2352)
```
DataViewField actually only has spec and kbnFieldType properties, spec
is of type FieldSpec which is basically the same type as BrowserField,
and has sufficient overlap for the (still unsafe, but more safe than as
unknown) cast to occur.

Before:
<img width="338" alt="image"
src="f31c1f9e-25f0-41ee-9e1c-a70171e41d29">


After:
<img width="555" alt="image"
src="8b462477-2dce-41bb-9592-f34b20634b84">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kevin Qualters 2023-05-31 17:13:36 -04:00 committed by GitHub
parent a21ddd02b4
commit 1c75903f92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 131 additions and 239 deletions

View file

@ -11,9 +11,14 @@ import type {
MappingRuntimeField,
MappingRuntimeFields,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { RuntimeFieldSpec, RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common';
import type { BoolQuery } from '@kbn/es-query';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
type RunTimeMappings =
| Record<string, Omit<RuntimeFieldSpec, 'type'> & { type: RuntimePrimitiveTypes }>
| undefined;
interface BoolAgg {
bool: BoolQuery;
}
@ -29,7 +34,7 @@ export interface GroupingQueryArgs {
from: string;
groupByField: string;
rootAggregations?: NamedAggregation[];
runtimeMappings?: MappingRuntimeFields;
runtimeMappings?: RunTimeMappings;
additionalAggregationsRoot?: NamedAggregation[];
pageNumber?: number;
uniqueValue: string;

View file

@ -27,8 +27,8 @@ import type {
TimelineRequestSortField,
TimelineStrategyResponseType,
} from '@kbn/timelines-plugin/common/search_strategy';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import { dataTableActions, Direction, TableId } from '@kbn/securitysolution-data-table';
import type { RunTimeMappings } from '../../store/sourcerer/model';
import { TimelineEventsQueries } from '../../../../common/search_strategy';
import type { KueryFilterQueryKind } from '../../../../common/types';
import type { ESQuery } from '../../../../common/typed_json';
@ -77,7 +77,7 @@ export interface UseTimelineEventsProps {
indexNames: string[];
language?: KueryFilterQueryKind;
limit: number;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
skip?: boolean;
sort?: TimelineRequestSortField[];
startDate: string;
@ -321,7 +321,7 @@ export const useTimelineEventsHandler = ({
querySize: prevRequest?.pagination.querySize ?? 0,
sort: prevRequest?.sort ?? initSortDefault,
timerange: prevRequest?.timerange ?? {},
runtimeMappings: prevRequest?.runtimeMappings ?? {},
runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as RunTimeMappings,
filterStatus: prevRequest?.filterStatus,
};

View file

@ -32,6 +32,7 @@ import {
import numeral from '@elastic/numeral';
import { css } from '@emotion/react';
import type { EuiMarkdownEditorUiPluginEditorProps } from '@elastic/eui/src/components/markdown_editor/markdown_types';
import { DataView } from '@kbn/data-views-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import type { Filter } from '@kbn/es-query';
import { FilterStateStore } from '@kbn/es-query';
@ -245,7 +246,16 @@ const InsightEditorComponent = ({
ui: { FiltersBuilderLazy },
},
uiSettings,
fieldFormats,
} = useKibana().services;
const dataView = useMemo(() => {
if (sourcererDataView != null) {
return new DataView({ spec: sourcererDataView, fieldFormats });
} else {
return null;
}
}, [sourcererDataView, fieldFormats]);
const [providers, setProviders] = useState<Provider[][]>([[]]);
const dateRangeChoices = useMemo(() => {
const settings: Array<{ from: string; to: string; display: string }> = uiSettings.get(
@ -419,11 +429,11 @@ const InsightEditorComponent = ({
/>
</EuiFormRow>
<EuiFormRow label={i18n.FILTER_BUILDER} helpText={i18n.FILTER_BUILDER_TEXT} fullWidth>
{sourcererDataView ? (
{dataView ? (
<FiltersBuilderLazy
filters={filtersStub}
onChange={onChange}
dataView={sourcererDataView}
dataView={dataView}
maxDepth={2}
/>
) : (

View file

@ -1,93 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`source/index.tsx getAllBrowserFields it returns an array of all fields in the BrowserFields argument 1`] = `
Array [
Object {
"aggregatable": true,
"count": 10,
"esTypes": Array [
"long",
],
"isMapped": true,
"name": "bytes",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "number",
},
Object {
"aggregatable": true,
"count": 20,
"esTypes": Array [
"boolean",
],
"isMapped": true,
"name": "ssl",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "boolean",
},
Object {
"aggregatable": true,
"count": 30,
"esTypes": Array [
"date",
],
"isMapped": true,
"name": "@timestamp",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "date",
},
]
`;
exports[`source/index.tsx getBrowserFields it transforms input into output as expected 1`] = `
Object {
"base": Object {
"fields": Object {
"@timestamp": Object {
"aggregatable": true,
"count": 30,
"esTypes": Array [
"date",
],
"isMapped": true,
"name": "@timestamp",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "date",
},
"bytes": Object {
"aggregatable": true,
"count": 10,
"esTypes": Array [
"long",
],
"isMapped": true,
"name": "bytes",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "number",
},
"ssl": Object {
"aggregatable": true,
"count": 20,
"esTypes": Array [
"boolean",
],
"isMapped": true,
"name": "ssl",
"readFromDocValues": true,
"scripted": false,
"searchable": true,
"type": "boolean",
},
},
},
}
`;

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import type { IndexField } from '../../../../common/search_strategy/index_fields';
import { getBrowserFields, getAllBrowserFields } from '.';
import type { IndexFieldSearch } from './use_data_view';
import { useDataView } from './use_data_view';
import { mocksSource } from './mock';
@ -27,32 +25,6 @@ jest.mock('../../lib/kibana');
jest.mock('../../lib/apm/use_track_http_request');
describe('source/index.tsx', () => {
describe('getAllBrowserFields', () => {
test('it returns an array of all fields in the BrowserFields argument', () => {
expect(
getAllBrowserFields(getBrowserFields('title 1', mocksSource.indexFields as IndexField[]))
).toMatchSnapshot();
});
});
describe('getBrowserFields', () => {
test('it returns an empty object given an empty array', () => {
const fields = getBrowserFields('title 1', []);
expect(fields).toEqual({});
});
test('it returns the same input given the same title and same fields length', () => {
const oldFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
const newFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
// Since it is memoized it will return the same object instance
expect(newFields).toBe(oldFields);
});
test('it transforms input into output as expected', () => {
const fields = getBrowserFields('title 2', mocksSource.indexFields as IndexField[]);
expect(fields).toMatchSnapshot();
});
});
describe('useDataView hook', () => {
const mockSearchResponse = {
...mocksSource,
@ -74,8 +46,8 @@ describe('source/index.tsx', () => {
data: {
dataViews: {
...useKibana().services.data.dataViews,
get: async (dataViewId: string, displayErrors?: boolean, refreshFields = false) =>
Promise.resolve({
get: async (dataViewId: string, displayErrors?: boolean, refreshFields = false) => {
const dataViewMock = {
id: dataViewId,
matchedIndices: refreshFields
? ['hello', 'world', 'refreshed']
@ -88,7 +60,12 @@ describe('source/index.tsx', () => {
type: 'keyword',
},
}),
}),
};
return Promise.resolve({
toSpec: () => dataViewMock,
...dataViewMock,
});
},
getFieldsForWildcard: async () => Promise.resolve(),
},
search: {
@ -178,7 +155,6 @@ describe('source/index.tsx', () => {
const {
payload: { patternList: newPatternList },
} = mockDispatch.mock.calls[1][0];
expect(patternList).not.toBe(newPatternList);
expect(patternList).not.toContain('refreshed*');
expect(newPatternList).toContain('refreshed*');

View file

@ -9,9 +9,9 @@ import { isEmpty, isEqual, keyBy, pick } from 'lodash/fp';
import memoizeOne from 'memoize-one';
import { useCallback, useEffect, useRef, useState } from 'react';
import type { DataViewBase } from '@kbn/es-query';
import type { BrowserField, BrowserFields, IndexField } from '@kbn/timelines-plugin/common';
import type { DataView, IIndexPatternFieldList } from '@kbn/data-views-plugin/common';
import { getCategory } from '@kbn/triggers-actions-ui-plugin/public';
import type { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common';
import type { IIndexPatternFieldList } from '@kbn/data-views-plugin/common';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import { useKibana } from '../../lib/kibana';
import * as i18n from './translations';
@ -72,35 +72,6 @@ export const getIndexFields = memoizeOne(
newArgs[2] === lastArgs[2]
);
/**
* HOT Code path where the fields can be 16087 in length or larger. This is
* VERY mutatious on purpose to improve the performance of the transform.
*/
export const getBrowserFields = memoizeOne(
(_title: string, fields: IndexField[]): BrowserFields => {
// Adds two dangerous casts to allow for mutations within this function
type DangerCastForMutation = Record<string, {}>;
type DangerCastForBrowserFieldsMutation = Record<
string,
Omit<BrowserField, 'fields'> & { fields: Record<string, BrowserField> }
>;
// We mutate this instead of using lodash/set to keep this as fast as possible
return fields.reduce<DangerCastForBrowserFieldsMutation>((accumulator, field) => {
const category = getCategory(field.name);
if (accumulator[category] == null) {
(accumulator as DangerCastForMutation)[category] = {};
}
if (accumulator[category].fields == null) {
accumulator[category].fields = {};
}
accumulator[category].fields[field.name] = field as unknown as BrowserField;
return accumulator;
}, {});
},
(newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length
);
const DEFAULT_BROWSER_FIELDS = {};
const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' };
interface FetchIndexReturn {
@ -115,7 +86,7 @@ interface FetchIndexReturn {
indexes: string[];
indexExists: boolean;
indexPatterns: DataViewBase;
dataView: DataView | undefined;
dataView: DataViewSpec | undefined;
}
/**
@ -149,9 +120,10 @@ export const useFetchIndex = (
setState({ ...state, loading: true });
abortCtrl.current = new AbortController();
const dv = await data.dataViews.create({ title: iNames.join(','), allowNoIndex: true });
const dataView = dv.toSpec();
const { browserFields } = getDataViewStateFromIndexFields(
iNames,
dv.fields,
dataView.fields,
includeUnmapped
);
@ -159,7 +131,7 @@ export const useFetchIndex = (
setState({
loading: false,
dataView: dv,
dataView,
browserFields,
indexes: dv.getIndexPattern().split(','),
indexExists: dv.getIndexPattern().split(',').length > 0,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { MappingRuntimeFieldType } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
@ -577,11 +577,13 @@ export const mockBrowserFields: BrowserFields = {
},
};
export const mockRuntimeMappings: MappingRuntimeFields = {
const runTimeType: MappingRuntimeFieldType = 'keyword' as const;
export const mockRuntimeMappings = {
'@a.runtime.field': {
script: {
source: 'emit("Radical dude: " + doc[\'host.name\'].value)',
},
type: 'keyword',
type: runTimeType,
},
};

View file

@ -9,9 +9,9 @@ import { useCallback, useRef } from 'react';
import type { Subscription } from 'rxjs';
import { useDispatch } from 'react-redux';
import memoizeOne from 'memoize-one';
import type { BrowserField } from '@kbn/timelines-plugin/common';
import type { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common';
import { getCategory } from '@kbn/triggers-actions-ui-plugin/public';
import type { DataViewFieldBase } from '@kbn/es-query';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import { useKibana } from '../../lib/kibana';
import { sourcererActions } from '../../store/sourcerer';
@ -50,34 +50,31 @@ interface DataViewInfo {
export const getDataViewStateFromIndexFields = memoizeOne(
(
_title: string,
fields: DataViewFieldBase[],
fields: DataViewSpec['fields'],
_includeUnmapped: boolean = false
): DataViewInfo => {
// Adds two dangerous casts to allow for mutations within this function
type DangerCastForMutation = Record<string, {}>;
return fields.reduce<DataViewInfo>(
(acc, field) => {
// mutate browserFields
const category = getCategory(field.name);
if (acc.browserFields[category] == null) {
(acc.browserFields as DangerCastForMutation)[category] = {};
if (fields == null) {
return { browserFields: {} };
} else {
const browserFields: BrowserFields = {};
for (const [name, field] of Object.entries(fields)) {
const category = getCategory(name);
if (browserFields[category] == null) {
(browserFields as DangerCastForMutation)[category] = { fields: {} };
}
if (acc.browserFields[category].fields == null) {
acc.browserFields[category].fields = {};
const categoryFields = browserFields[category].fields;
if (categoryFields) {
categoryFields[name] = field as BrowserField;
}
acc.browserFields[category].fields[field.name] = field as unknown as BrowserField;
return acc;
},
{
browserFields: {},
}
);
return { browserFields: browserFields as DangerCastForBrowserFieldsMutation };
}
},
(newArgs, lastArgs) =>
newArgs[0] === lastArgs[0] &&
newArgs[1].length === lastArgs[1].length &&
newArgs[1]?.length === lastArgs[1]?.length &&
newArgs[2] === lastArgs[2]
);
@ -89,7 +86,6 @@ export const useDataView = (): {
const searchSubscription$ = useRef<Record<string, Subscription>>({});
const dispatch = useDispatch();
const { addError } = useAppToasts();
const setLoading = useCallback(
({ id, loading }: { id: string; loading: boolean }) => {
dispatch(sourcererActions.setDataViewLoading({ id, loading }));
@ -113,7 +109,6 @@ export const useDataView = (): {
setLoading({ id: dataViewId, loading: true });
const dataView = await getSourcererDataView(dataViewId, data.dataViews, cleanCache);
if (needToBeInit && scopeId && !skipScopeUpdate) {
dispatch(
sourcererActions.setSelectedDataView({

View file

@ -7,7 +7,7 @@
import type { DataViewsContract } from '@kbn/data-views-plugin/common';
import { ensurePatternFormat } from '../../../../common/utils/sourcerer';
import type { SourcererDataView } from '../../store/sourcerer/model';
import type { SourcererDataView, RunTimeMappings } from '../../store/sourcerer/model';
import { getDataViewStateFromIndexFields } from '../source/use_data_view';
export const getSourcererDataView = async (
@ -15,8 +15,9 @@ export const getSourcererDataView = async (
dataViewsService: DataViewsContract,
refreshFields = false
): Promise<SourcererDataView> => {
const dataViewData = await dataViewsService.get(dataViewId, true, refreshFields);
const defaultPatternsList = ensurePatternFormat(dataViewData.getIndexPattern().split(','));
const dataView = await dataViewsService.get(dataViewId, true, refreshFields);
const dataViewData = dataView.toSpec();
const defaultPatternsList = ensurePatternFormat(dataView.getIndexPattern().split(','));
// typeguard used to assert that pattern is a string, otherwise
// typescript expects patternList to be (string | null)[]
@ -45,15 +46,13 @@ export const getSourcererDataView = async (
return {
loading: false,
id: dataViewData.id ?? '',
title: dataViewData.getIndexPattern(),
indexFields: dataViewData.fields,
title: dataView.getIndexPattern(),
indexFields: dataView.fields,
fields: dataViewData.fields,
patternList,
dataView: dataViewData,
browserFields: getDataViewStateFromIndexFields(
dataViewData.id ?? '',
dataViewData.fields != null ? dataViewData.fields : []
).browserFields,
runtimeMappings: dataViewData.getRuntimeMappings(),
browserFields: getDataViewStateFromIndexFields(dataViewData.id ?? '', dataViewData.fields)
.browserFields,
runtimeMappings: dataViewData.runtimeFieldMap as RunTimeMappings,
};
};

View file

@ -245,7 +245,7 @@ describe('Sourcerer Hooks', () => {
type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING',
payload: { loading: false },
});
expect(mockDispatch).toHaveBeenCalledTimes(9);
expect(mockDispatch).toHaveBeenCalledTimes(7);
expect(mockSearch).toHaveBeenCalledTimes(2);
});
});

View file

@ -14,6 +14,7 @@ import type {
SelectedDataView,
SourcererDataView,
SourcererUrlState,
RunTimeMappings,
} from '../../store/sourcerer/model';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { useUserInfo } from '../../../detections/components/user_info';
@ -395,14 +396,14 @@ export const useSourcererDataView = (
() => ({
...fetchIndexReturn,
dataView: fetchIndexReturn.dataView,
runtimeMappings: fetchIndexReturn.dataView?.getRuntimeMappings() ?? {},
title: fetchIndexReturn.dataView?.getIndexPattern() ?? '',
runtimeMappings: (fetchIndexReturn.dataView?.runtimeFieldMap as RunTimeMappings) ?? {},
title: fetchIndexReturn.dataView?.title ?? '',
id: fetchIndexReturn.dataView?.id ?? null,
loading: indexPatternsLoading,
patternList: fetchIndexReturn.indexes,
indexFields: fetchIndexReturn.indexPatterns
.fields as SelectedDataView['indexPattern']['fields'],
fields: fetchIndexReturn.indexPatterns.fields,
fields: fetchIndexReturn.dataView?.fields,
}),
[fetchIndexReturn, indexPatternsLoading]
);

View file

@ -6,6 +6,7 @@
*/
import { TableId } from '@kbn/securitysolution-data-table';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import { InputsModelId } from '../store/inputs/constants';
import {
Direction,
@ -48,6 +49,10 @@ import { UsersFields } from '../../../common/search_strategy/security_solution/u
import { initialGroupingState } from '../store/grouping/reducer';
import type { SourcererState } from '../store/sourcerer';
const mockFieldMap: DataViewSpec['fields'] = Object.fromEntries(
mockIndexFields.map((field) => [field.name, field])
);
export const mockSourcererState: SourcererState = {
...initialSourcererState,
signalIndexName: `${DEFAULT_SIGNALS_INDEX}-spacename`,
@ -56,7 +61,7 @@ export const mockSourcererState: SourcererState = {
browserFields: mockBrowserFields,
id: DEFAULT_DATA_VIEW_ID,
indexFields: mockIndexFields,
fields: mockIndexFields,
fields: mockFieldMap,
loading: false,
patternList: [...DEFAULT_INDEX_PATTERN, `${DEFAULT_SIGNALS_INDEX}-spacename`],
runtimeMappings: mockRuntimeMappings,

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { DataViewFieldBase } from '@kbn/es-query';
import type { BrowserFields } from '@kbn/timelines-plugin/common';
import { EMPTY_BROWSER_FIELDS, EMPTY_INDEX_FIELDS } from '@kbn/timelines-plugin/common';
import type { DataViewSpec } from '@kbn/data-views-plugin/public';
import type { RuntimeFieldSpec, RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common';
import type { SecuritySolutionDataViewBase } from '../../types';
/** Uniquely identifies a Sourcerer Scope */
export enum SourcererScopeName {
default = 'default',
@ -52,6 +52,10 @@ export interface KibanaDataView {
title: string;
}
export type RunTimeMappings =
| Record<string, Omit<RuntimeFieldSpec, 'type'> & { type: RuntimePrimitiveTypes }>
| undefined;
/**
* DataView from Kibana + timelines/index_fields enhanced field data
*/
@ -68,7 +72,7 @@ export interface SourcererDataView extends KibanaDataView {
* @deprecated use sourcererDataView.fields
* comes from dataView.fields.toSpec() */
indexFields: SecuritySolutionDataViewBase['fields'];
fields: DataViewFieldBase[];
fields: DataViewSpec['fields'] | undefined;
/** set when data view fields are fetched */
loading: boolean;
/**
@ -76,11 +80,11 @@ export interface SourcererDataView extends KibanaDataView {
* Needed to pass to search strategy
* Remove once issue resolved: https://github.com/elastic/kibana/issues/111762
*/
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
/**
* @type DataView @kbn/data-views-plugin/common
*/
dataView: DataView | undefined;
dataView: DataViewSpec | undefined;
}
/**
@ -125,7 +129,7 @@ export interface SelectedDataView {
* Easier to add this additional data rather than
* try to extend the SelectedDataView type from DataView.
*/
sourcererDataView: DataView | undefined;
sourcererDataView: DataViewSpec | undefined;
}
/**
@ -155,11 +159,12 @@ export const initSourcererScope: Omit<SourcererScope, 'id'> = {
selectedPatterns: [],
missingPatterns: [],
};
export const initDataView: SourcererDataView & { id: string; error?: unknown } = {
browserFields: EMPTY_BROWSER_FIELDS,
id: '',
indexFields: EMPTY_INDEX_FIELDS,
fields: EMPTY_INDEX_FIELDS,
fields: undefined,
loading: false,
patternList: [],
runtimeMappings: {},

View file

@ -6,21 +6,21 @@
*/
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import { useDispatch } from 'react-redux';
import type { Filter, Query } from '@kbn/es-query';
import { isNoneGroup, useGrouping } from '@kbn/securitysolution-grouping';
import { isEmpty, isEqual } from 'lodash/fp';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import type { TableIdLiteral } from '@kbn/securitysolution-data-table';
import { getDefaultGroupingOptions } from '../../../common/utils/alerts';
import { groupIdSelector } from '../../../common/store/grouping/selectors';
import { getDefaultGroupingOptions } from '../../../common/utils/alerts';
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { updateGroups } from '../../../common/store/grouping/actions';
import type { Status } from '../../../../common/detection_engine/schemas/common';
import { defaultUnit } from '../../../common/components/toolbar/unit';
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import type { RunTimeMappings } from '../../../common/store/sourcerer/model';
import { renderGroupPanel, getStats } from './grouping_settings';
import { useKibana } from '../../../common/lib/kibana';
import { GroupedSubLevel } from './alerts_sub_grouping';
@ -36,7 +36,7 @@ export interface AlertsTableComponentProps {
hasIndexWrite: boolean;
loading: boolean;
renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
signalIndexName: string | null;
tableId: TableIdLiteral;
to: string;

View file

@ -13,9 +13,9 @@ import type { GroupingAggregation } from '@kbn/securitysolution-grouping';
import { isNoneGroup } from '@kbn/securitysolution-grouping';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
import type { DynamicGroupingProps } from '@kbn/securitysolution-grouping/src';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import type { TableIdLiteral } from '@kbn/securitysolution-data-table';
import { parseGroupingQuery } from '@kbn/securitysolution-grouping/src';
import type { RunTimeMappings } from '../../../common/store/sourcerer/model';
import { combineQueries } from '../../../common/lib/kuery';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import type { AlertsGroupingAggregation } from './grouping_settings/types';
@ -53,7 +53,7 @@ interface OwnProps {
pageSize: number;
parentGroupingFilter?: string;
renderChildComponent: (groupingFilters: Filter[]) => React.ReactElement;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
selectedGroup: string;
setPageIndex: (newIndex: number) => void;
setPageSize: (newSize: number) => void;

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
import type { BoolQuery } from '@kbn/es-query';
import type { NamedAggregation } from '@kbn/securitysolution-grouping';
import { isNoneGroup, getGroupingQuery } from '@kbn/securitysolution-grouping';
import type { RunTimeMappings } from '../../../../common/store/sourcerer/model';
interface AlertsGroupingQueryParams {
additionalFilters: Array<{
@ -17,7 +17,7 @@ interface AlertsGroupingQueryParams {
from: string;
pageIndex: number;
pageSize: number;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
selectedGroup: string;
uniqueValue: string;
to: string;

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Filter, Query } from '@kbn/es-query';
import { EuiFlexItem, EuiSkeletonText } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
@ -19,6 +18,7 @@ import { ChartSelect } from './chart_select';
import { ChartCollapse } from './chart_collapse';
import * as i18n from './chart_select/translations';
import { AlertsTreemapPanel } from '../../../../common/components/alerts_treemap_panel';
import type { RunTimeMappings } from '../../../../common/store/sourcerer/model';
import type { UpdateDateRange } from '../../../../common/components/charts/common';
import { useEuiComboBoxReset } from '../../../../common/components/use_combo_box_reset';
import { AlertsHistogramPanel } from '../../../components/alerts_kpis/alerts_histogram_panel';
@ -51,7 +51,7 @@ export interface Props {
alertsDefaultFilters: Filter[];
isLoadingIndexPattern: boolean;
query: Query;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
signalIndexName: string | null;
updateDateRangeCallback: UpdateDateRange;
}

View file

@ -9,12 +9,12 @@ import { EuiSpacer, EuiFlyoutBody } from '@elastic/eui';
import React, { useMemo } from 'react';
import deepEqual from 'fast-deep-equal';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { BrowserFields } from '../../../../common/containers/source';
import { ExpandableEvent, ExpandableEventTitle } from './expandable_event';
import { useTimelineEventsDetails } from '../../../containers/details';
import type { TimelineTabs } from '../../../../../common/types/timeline';
import type { RunTimeMappings } from '../../../../common/store/sourcerer/model';
import { useHostIsolationTools } from './use_host_isolation_tools';
import { FlyoutBody, FlyoutHeader, FlyoutFooter } from './flyout';
import { useBasicDataFromDetailsData, getAlertIndexAlias } from './helpers';
@ -33,7 +33,7 @@ interface EventDetailsPanelProps {
handleOnEventClosed: () => void;
isDraggable?: boolean;
isFlyoutView?: boolean;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
tabType: TimelineTabs;
scopeId: string;
isReadOnly?: boolean;

View file

@ -10,13 +10,13 @@ import { useDispatch } from 'react-redux';
import type { EuiFlyoutProps } from '@elastic/eui';
import { EuiFlyout } from '@elastic/eui';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { EntityType } from '@kbn/timelines-plugin/common';
import { dataTableActions, dataTableSelectors } from '@kbn/securitysolution-data-table';
import { getScopedActions, isInTableScope, isTimelineScope } from '../../../helpers';
import { timelineSelectors } from '../../store/timeline';
import { timelineDefaults } from '../../store/timeline/defaults';
import type { BrowserFields } from '../../../common/containers/source';
import type { RunTimeMappings } from '../../../common/store/sourcerer/model';
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { EventDetailsPanel } from './event_details';
@ -30,7 +30,7 @@ interface DetailsPanelProps {
entityType?: EntityType;
handleOnPanelClosed?: () => void;
isFlyoutView?: boolean;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
tabType?: TimelineTabs;
scopeId: string;
isReadOnly?: boolean;

View file

@ -11,11 +11,11 @@ import ReactDOM from 'react-dom';
import deepEqual from 'fast-deep-equal';
import { Subscription } from 'rxjs';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/common';
import { EntityType } from '@kbn/timelines-plugin/common';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { useKibana } from '../../../common/lib/kibana';
import type { RunTimeMappings } from '../../../common/store/sourcerer/model';
import type {
SearchHit,
TimelineEventsDetailsItem,
@ -35,7 +35,7 @@ export interface UseTimelineEventsDetailsProps {
entityType?: EntityType;
indexName: string;
eventId: string;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
skip: boolean;
}

View file

@ -11,12 +11,12 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Subscription } from 'rxjs';
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { DataView } from '@kbn/data-plugin/common';
import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/common';
import type { ESQuery } from '../../../common/typed_json';
import type { inputsModel } from '../../common/store';
import type { RunTimeMappings } from '../../common/store/sourcerer/model';
import { useKibana } from '../../common/lib/kibana';
import { createFilter } from '../../common/containers/helpers';
import { timelineActions } from '../store/timeline';
@ -89,7 +89,7 @@ export interface UseTimelineEventsProps {
indexNames: string[];
language?: KueryFilterQueryKind;
limit: number;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
skip?: boolean;
sort?: TimelineRequestSortField[];
startDate?: string;
@ -356,7 +356,7 @@ export const useTimelineEventsHandler = ({
querySize: prevRequest?.pagination.querySize ?? 0,
sort: prevRequest?.sort ?? initSortDefault,
timerange: prevRequest?.timerange ?? {},
runtimeMappings: prevRequest?.runtimeMappings ?? {},
runtimeMappings: (prevRequest?.runtimeMappings ?? {}) as RunTimeMappings,
...deStructureEqlOptions(prevEqlRequest),
};

View file

@ -10,6 +10,7 @@ import type { BehaviorSubject, Observable } from 'rxjs';
import type { AppLeaveHandler, CoreStart } from '@kbn/core/public';
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common';
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { NewsfeedPublicPluginStart } from '@kbn/newsfeed-plugin/public';
@ -108,6 +109,7 @@ export interface StartPlugins {
threatIntelligence: ThreatIntelligencePluginStart;
cloudExperiments?: CloudExperimentsPluginStart;
dataViews: DataViewsServicePublic;
fieldFormats: FieldFormatsStartCommon;
}
export interface StartPluginsDependencies extends StartPlugins {

View file

@ -157,6 +157,7 @@
"@kbn/ecs",
"@kbn/url-state",
"@kbn/ml-anomaly-utils",
"@kbn/field-formats-plugin",
"@kbn/dev-proc-runner"
]
}

View file

@ -78,7 +78,7 @@ export interface BrowserField {
category: string;
description: string | null;
example: string | number | null;
fields: Readonly<Record<string, Partial<BrowserField>>>;
fields: Record<string, Partial<BrowserField>>;
format: string;
indexes: string[];
name: string;
@ -97,7 +97,7 @@ export interface BrowserField {
* you are working with? Or perhaps you need a description for a
* particular field? Consider using the EcsFlat module from `@kbn/ecs`
*/
export type BrowserFields = Readonly<Record<string, Partial<BrowserField>>>;
export type BrowserFields = Record<string, Partial<BrowserField>>;
export const EMPTY_BROWSER_FIELDS = {};
export const EMPTY_INDEX_FIELDS: FieldSpec[] = [];

View file

@ -7,11 +7,11 @@
import { JsonObject } from '@kbn/utility-types';
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { IEsSearchResponse } from '@kbn/data-plugin/common';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import type { CursorType, Inspect, Maybe, PaginationInputPaginated } from '../../../common';
import type { TimelineRequestOptionsPaginated } from '../..';
import type { RunTimeMappings } from '../eql';
export interface TimelineEdges {
node: TimelineItem;
@ -45,6 +45,6 @@ export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsP
fieldRequested: string[];
fields: string[] | Array<{ field: string; include_unmapped: boolean }>;
language: 'eql' | 'kuery' | 'lucene';
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
filterStatus?: AlertWorkflowStatus;
}

View file

@ -6,10 +6,20 @@
*/
import { EuiComboBoxOptionOption } from '@elastic/eui';
import type { EqlSearchStrategyRequest, EqlSearchStrategyResponse } from '@kbn/data-plugin/common';
import type {
EqlSearchStrategyRequest,
EqlSearchStrategyResponse,
EqlRequestParams,
} from '@kbn/data-plugin/common';
import type { RuntimeFieldSpec, RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common';
import { EqlSearchResponse, Inspect, Maybe, PaginationInputPaginated } from '../../..';
import { TimelineEdges, TimelineEventsAllRequestOptions } from '../..';
type EqlBody = Pick<EqlRequestParams, 'body'>;
export type RunTimeMappings =
| Record<string, Omit<RuntimeFieldSpec, 'type'> & { type: RuntimePrimitiveTypes }>
| undefined;
export interface TimelineEqlRequestOptions
extends EqlSearchStrategyRequest,
Omit<TimelineEventsAllRequestOptions, 'params'> {
@ -17,6 +27,8 @@ export interface TimelineEqlRequestOptions
tiebreakerField?: string;
timestampField?: string;
size?: number;
runtime_mappings?: RunTimeMappings;
body?: Omit<EqlRequestParams, 'body'> & EqlBody & { runtime_mappings?: RunTimeMappings };
}
export interface TimelineEqlResponse extends EqlSearchStrategyResponse<EqlSearchResponse<unknown>> {

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { IEsSearchRequest } from '@kbn/data-plugin/common';
import { ESQuery } from '../../typed_json';
import {
@ -26,6 +25,7 @@ import {
TimelineStatus,
RowRendererId,
} from '../../types/timeline';
import type { RunTimeMappings } from './events/eql';
export * from './events';
@ -37,7 +37,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest {
defaultIndex: string[];
factoryQueryType?: TimelineFactoryQueryTypes;
entityType?: EntityType;
runtimeMappings: MappingRuntimeFields;
runtimeMappings: RunTimeMappings;
}
export interface TimelineRequestSortField<Field = string> extends SortField<Field> {