mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solutions] Removes deprecated types in kbn-securitysolution-* for newer kbn-es-query types (#106801)
## Summary Fixes https://github.com/elastic/kibana/issues/105731, by replacing these `any` types: ```json type IFieldType = any; type IIndexPattern = any; type Filter = any; ``` With the types from `es-query` which are: * IndexPatternFieldBase * IndexPatternBase * Filter Note: I had to do a few creative casting to avoid having to use `FieldSpec` since that is not within the package `es-query` and is not planned to be within that package or another package for at least a while if ever. ### 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
This commit is contained in:
parent
fcaa4aaf40
commit
5dd68dd7b3
39 changed files with 399 additions and 399 deletions
|
@ -36,6 +36,7 @@ SRC_DEPS = [
|
|||
"//packages/kbn-i18n",
|
||||
"//packages/kbn-securitysolution-io-ts-list-types",
|
||||
"//packages/kbn-securitysolution-list-hooks",
|
||||
"//packages/kbn-es-query",
|
||||
"@npm//@babel/core",
|
||||
"@npm//babel-loader",
|
||||
"@npm//@elastic/eui",
|
||||
|
|
|
@ -12,7 +12,7 @@ This hook uses the kibana `services.data.autocomplete.getValueSuggestions()` ser
|
|||
|
||||
This component can be used to display available indexPattern fields. It requires an indexPattern to be passed in and will show an error state if value is not one of the available indexPattern fields. Users will be able to select only one option.
|
||||
|
||||
The `onChange` handler is passed `IFieldType[]`.
|
||||
The `onChange` handler is passed `IndexPatternFieldBase[]`.
|
||||
|
||||
```js
|
||||
<FieldComponent
|
||||
|
|
|
@ -6,18 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
|
||||
/**
|
||||
* Determines if empty value is ok
|
||||
*/
|
||||
export const checkEmptyValue = (
|
||||
param: string | undefined,
|
||||
field: IFieldType | undefined,
|
||||
field: IndexPatternFieldBase | undefined,
|
||||
isRequired: boolean,
|
||||
touched: boolean
|
||||
): string | undefined | null => {
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
type IIndexPattern = any;
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import {
|
||||
getGenericComboBoxProps,
|
||||
|
@ -24,14 +20,14 @@ const AS_PLAIN_TEXT = { asPlainText: true };
|
|||
interface OperatorProps {
|
||||
fieldInputWidth?: number;
|
||||
fieldTypeFilter?: string[];
|
||||
indexPattern: IIndexPattern | undefined;
|
||||
indexPattern: IndexPatternBase | undefined;
|
||||
isClearable: boolean;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
isRequired?: boolean;
|
||||
onChange: (a: IFieldType[]) => void;
|
||||
onChange: (a: IndexPatternFieldBase[]) => void;
|
||||
placeholder: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
}
|
||||
|
||||
export const FieldComponent: React.FC<OperatorProps> = ({
|
||||
|
@ -60,7 +56,7 @@ export const FieldComponent: React.FC<OperatorProps> = ({
|
|||
|
||||
const handleValuesChange = useCallback(
|
||||
(newOptions: EuiComboBoxOptionOption[]): void => {
|
||||
const newValues: IFieldType[] = newOptions.map(
|
||||
const newValues: IndexPatternFieldBase[] = newOptions.map(
|
||||
({ label }) => availableFields[labels.indexOf(label)]
|
||||
);
|
||||
onChange(newValues);
|
||||
|
@ -98,13 +94,13 @@ export const FieldComponent: React.FC<OperatorProps> = ({
|
|||
FieldComponent.displayName = 'Field';
|
||||
|
||||
interface ComboBoxFields {
|
||||
availableFields: IFieldType[];
|
||||
selectedFields: IFieldType[];
|
||||
availableFields: IndexPatternFieldBase[];
|
||||
selectedFields: IndexPatternFieldBase[];
|
||||
}
|
||||
|
||||
const getComboBoxFields = (
|
||||
indexPattern: IIndexPattern | undefined,
|
||||
selectedField: IFieldType | undefined,
|
||||
indexPattern: IndexPatternBase | undefined,
|
||||
selectedField: IndexPatternFieldBase | undefined,
|
||||
fieldTypeFilter: string[]
|
||||
): ComboBoxFields => {
|
||||
const existingFields = getExistingFields(indexPattern);
|
||||
|
@ -117,27 +113,29 @@ const getComboBoxFields = (
|
|||
const getComboBoxProps = (fields: ComboBoxFields): GetGenericComboBoxPropsReturn => {
|
||||
const { availableFields, selectedFields } = fields;
|
||||
|
||||
return getGenericComboBoxProps<IFieldType>({
|
||||
return getGenericComboBoxProps<IndexPatternFieldBase>({
|
||||
getLabel: (field) => field.name,
|
||||
options: availableFields,
|
||||
selectedOptions: selectedFields,
|
||||
});
|
||||
};
|
||||
|
||||
const getExistingFields = (indexPattern: IIndexPattern | undefined): IFieldType[] => {
|
||||
const getExistingFields = (indexPattern: IndexPatternBase | undefined): IndexPatternFieldBase[] => {
|
||||
return indexPattern != null ? indexPattern.fields : [];
|
||||
};
|
||||
|
||||
const getSelectedFields = (selectedField: IFieldType | undefined): IFieldType[] => {
|
||||
const getSelectedFields = (
|
||||
selectedField: IndexPatternFieldBase | undefined
|
||||
): IndexPatternFieldBase[] => {
|
||||
return selectedField ? [selectedField] : [];
|
||||
};
|
||||
|
||||
const getAvailableFields = (
|
||||
existingFields: IFieldType[],
|
||||
selectedFields: IFieldType[],
|
||||
existingFields: IndexPatternFieldBase[],
|
||||
selectedFields: IndexPatternFieldBase[],
|
||||
fieldTypeFilter: string[]
|
||||
): IFieldType[] => {
|
||||
const fieldsByName = new Map<string, IFieldType>();
|
||||
): IndexPatternFieldBase[] => {
|
||||
const fieldsByName = new Map<string, IndexPatternFieldBase>();
|
||||
|
||||
existingFields.forEach((f) => fieldsByName.set(f.name, f));
|
||||
selectedFields.forEach((f) => fieldsByName.set(f.name, f));
|
||||
|
|
|
@ -10,14 +10,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
|
||||
import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { useFindLists } from '@kbn/securitysolution-list-hooks';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import { filterFieldToList } from '../filter_field_to_list';
|
||||
import { getGenericComboBoxProps } from '../get_generic_combo_box_props';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715
|
||||
// import { HttpStart } from 'kibana/public';
|
||||
type HttpStart = any;
|
||||
|
@ -34,7 +31,7 @@ interface AutocompleteFieldListsProps {
|
|||
onChange: (arg: ListSchema) => void;
|
||||
placeholder: string;
|
||||
rowLabel?: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
selectedValue: string | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('ip')}
|
||||
selectedValue="126.45.211.34"
|
||||
selectedValue="127.0.0.1"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -79,7 +79,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('ip')}
|
||||
selectedValue="126.45.211.34"
|
||||
selectedValue="127.0.0.1"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -104,7 +104,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('ip')}
|
||||
selectedValue="126.45.211.34"
|
||||
selectedValue="127.0.0.1"
|
||||
/>
|
||||
);
|
||||
wrapper.find('[data-test-subj="valuesAutocompleteMatch"] button').at(0).simulate('click');
|
||||
|
@ -131,7 +131,7 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('ip')}
|
||||
selectedValue="126.45.211.34"
|
||||
selectedValue="127.0.0.1"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -158,13 +158,13 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('ip')}
|
||||
selectedValue="126.45.211.34"
|
||||
selectedValue="127.0.0.1"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="valuesAutocompleteMatch"] EuiComboBoxPill').at(0).text()
|
||||
).toEqual('126.45.211.34');
|
||||
).toEqual('127.0.0.1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value created', async () => {
|
||||
|
@ -190,9 +190,9 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
|
||||
((wrapper.find(EuiComboBox).props() as unknown) as {
|
||||
onCreateOption: (a: string) => void;
|
||||
}).onCreateOption('126.45.211.34');
|
||||
}).onCreateOption('127.0.0.1');
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('126.45.211.34');
|
||||
expect(mockOnChange).toHaveBeenCalledWith('127.0.0.1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value selected', async () => {
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
EuiComboBoxOptionOption,
|
||||
EuiComboBox,
|
||||
} from '@elastic/eui';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
@ -22,11 +24,6 @@ import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-
|
|||
// import { AutocompleteStart } from '../../../../../../../src/plugins/data/public';
|
||||
type AutocompleteStart = any;
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
type IIndexPattern = any;
|
||||
|
||||
import * as i18n from '../translations';
|
||||
import { useFieldValueAutocomplete } from '../hooks/use_field_value_autocomplete';
|
||||
import {
|
||||
|
@ -44,9 +41,9 @@ const SINGLE_SELECTION = { asPlainText: true };
|
|||
|
||||
interface AutocompleteFieldMatchProps {
|
||||
placeholder: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
selectedValue: string | undefined;
|
||||
indexPattern: IIndexPattern | undefined;
|
||||
indexPattern: IndexPatternBase | undefined;
|
||||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isClearable: boolean;
|
||||
|
|
|
@ -64,7 +64,7 @@ describe('AutocompleteFieldMatchAnyComponent', () => {
|
|||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('ip')}
|
||||
selectedValue={['126.45.211.34']}
|
||||
selectedValue={['127.0.0.1']}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -124,7 +124,7 @@ describe('AutocompleteFieldMatchAnyComponent', () => {
|
|||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('ip')}
|
||||
selectedValue={['126.45.211.34']}
|
||||
selectedValue={['127.0.0.1']}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -155,13 +155,13 @@ describe('AutocompleteFieldMatchAnyComponent', () => {
|
|||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('ip')}
|
||||
selectedValue={['126.45.211.34']}
|
||||
selectedValue={['127.0.0.1']}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="valuesAutocompleteMatchAny"] EuiComboBoxPill`).at(0).text()
|
||||
).toEqual('126.45.211.34');
|
||||
).toEqual('127.0.0.1');
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value created', async () => {
|
||||
|
@ -191,9 +191,9 @@ describe('AutocompleteFieldMatchAnyComponent', () => {
|
|||
|
||||
((wrapper.find(EuiComboBox).props() as unknown) as {
|
||||
onCreateOption: (a: string) => void;
|
||||
}).onCreateOption('126.45.211.34');
|
||||
}).onCreateOption('127.0.0.1');
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(['126.45.211.34']);
|
||||
expect(mockOnChange).toHaveBeenCalledWith(['127.0.0.1']);
|
||||
});
|
||||
|
||||
test('it invokes "onChange" when new value selected', async () => {
|
||||
|
|
|
@ -10,16 +10,12 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
|
||||
import { uniq } from 'lodash';
|
||||
import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715
|
||||
// import { AutocompleteStart } from '../../../../../../../src/plugins/data/public';
|
||||
type AutocompleteStart = any;
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
type IIndexPattern = any;
|
||||
|
||||
import * as i18n from '../translations';
|
||||
import {
|
||||
getGenericComboBoxProps,
|
||||
|
@ -30,9 +26,9 @@ import { paramIsValid } from '../param_is_valid';
|
|||
|
||||
interface AutocompleteFieldMatchAnyProps {
|
||||
placeholder: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
selectedValue: string[];
|
||||
indexPattern: IIndexPattern | undefined;
|
||||
indexPattern: IndexPatternBase | undefined;
|
||||
isLoading: boolean;
|
||||
isDisabled: boolean;
|
||||
isClearable: boolean;
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// Copied from "src/plugins/data/common/index_patterns/fields/fields.mocks.ts"
|
||||
// but without types.
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
// Copied from "src/plugins/data/common/index_patterns/fields/fields.mocks.ts" but with the types changed to "IndexPatternFieldBase" since that type is compatible.
|
||||
// TODO: This should move out once those mocks are directly useable or in their own package, https://github.com/elastic/kibana/issues/100715
|
||||
|
||||
export const fields = [
|
||||
export const fields: IndexPatternFieldBase[] = ([
|
||||
{
|
||||
name: 'bytes',
|
||||
type: 'number',
|
||||
|
@ -308,6 +309,6 @@ export const fields = [
|
|||
readFromDocValues: false,
|
||||
subType: { nested: { path: 'nestedField.nestedChild' } },
|
||||
},
|
||||
];
|
||||
] as unknown) as IndexPatternFieldBase[];
|
||||
|
||||
export const getField = (name: string) => fields.find((field) => field.name === name);
|
||||
|
|
|
@ -10,10 +10,7 @@ import { filterFieldToList } from '.';
|
|||
|
||||
import type { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { getListResponseMock } from '../list_schema/index.mock';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
describe('#filterFieldToList', () => {
|
||||
test('it returns empty array if given a undefined for field', () => {
|
||||
|
@ -22,13 +19,20 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns empty array if filed does not contain esTypes', () => {
|
||||
const field: IFieldType = { name: 'some-name', type: 'some-type' };
|
||||
const field: IndexPatternFieldBase = {
|
||||
name: 'some-name',
|
||||
type: 'some-type',
|
||||
};
|
||||
const filter = filterFieldToList([], field);
|
||||
expect(filter).toEqual([]);
|
||||
});
|
||||
|
||||
test('it returns single filtered list of ip_range -> ip', () => {
|
||||
const field: IFieldType = { esTypes: ['ip'], name: 'some-name', type: 'ip' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['ip'],
|
||||
name: 'some-name',
|
||||
type: 'ip',
|
||||
};
|
||||
const listItem: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
|
||||
const filter = filterFieldToList([listItem], field);
|
||||
const expected: ListSchema[] = [listItem];
|
||||
|
@ -36,7 +40,11 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns single filtered list of ip -> ip', () => {
|
||||
const field: IFieldType = { esTypes: ['ip'], name: 'some-name', type: 'ip' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['ip'],
|
||||
name: 'some-name',
|
||||
type: 'ip',
|
||||
};
|
||||
const listItem: ListSchema = { ...getListResponseMock(), type: 'ip' };
|
||||
const filter = filterFieldToList([listItem], field);
|
||||
const expected: ListSchema[] = [listItem];
|
||||
|
@ -44,7 +52,11 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns single filtered list of keyword -> keyword', () => {
|
||||
const field: IFieldType = { esTypes: ['keyword'], name: 'some-name', type: 'keyword' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['keyword'],
|
||||
name: 'some-name',
|
||||
type: 'keyword',
|
||||
};
|
||||
const listItem: ListSchema = { ...getListResponseMock(), type: 'keyword' };
|
||||
const filter = filterFieldToList([listItem], field);
|
||||
const expected: ListSchema[] = [listItem];
|
||||
|
@ -52,7 +64,11 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns single filtered list of text -> text', () => {
|
||||
const field: IFieldType = { esTypes: ['text'], name: 'some-name', type: 'text' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['text'],
|
||||
name: 'some-name',
|
||||
type: 'text',
|
||||
};
|
||||
const listItem: ListSchema = { ...getListResponseMock(), type: 'text' };
|
||||
const filter = filterFieldToList([listItem], field);
|
||||
const expected: ListSchema[] = [listItem];
|
||||
|
@ -60,7 +76,11 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns 2 filtered lists of ip_range -> ip', () => {
|
||||
const field: IFieldType = { esTypes: ['ip'], name: 'some-name', type: 'ip' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['ip'],
|
||||
name: 'some-name',
|
||||
type: 'ip',
|
||||
};
|
||||
const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
|
||||
const listItem2: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
|
||||
const filter = filterFieldToList([listItem1, listItem2], field);
|
||||
|
@ -69,7 +89,11 @@ describe('#filterFieldToList', () => {
|
|||
});
|
||||
|
||||
test('it returns 1 filtered lists of ip_range -> ip if the 2nd is not compatible type', () => {
|
||||
const field: IFieldType = { esTypes: ['ip'], name: 'some-name', type: 'ip' };
|
||||
const field: IndexPatternFieldBase & { esTypes: string[] } = {
|
||||
esTypes: ['ip'],
|
||||
name: 'some-name',
|
||||
type: 'ip',
|
||||
};
|
||||
const listItem1: ListSchema = { ...getListResponseMock(), type: 'ip_range' };
|
||||
const listItem2: ListSchema = { ...getListResponseMock(), type: 'text' };
|
||||
const filter = filterFieldToList([listItem1, listItem2], field);
|
||||
|
|
|
@ -7,19 +7,23 @@
|
|||
*/
|
||||
|
||||
import { ListSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { typeMatch } from '../type_match';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
|
||||
/**
|
||||
* Given an array of lists and optionally a field this will return all
|
||||
* the lists that match against the field based on the types from the field
|
||||
*
|
||||
* NOTE: That we support one additional property from "FieldSpec" located here:
|
||||
* src/plugins/data/common/index_patterns/fields/types.ts
|
||||
* This type property is esTypes. If it exists and is on there we will read off the esTypes.
|
||||
* @param lists The lists to match against the field
|
||||
* @param field The field to check against the list to see if they are compatible
|
||||
*/
|
||||
export const filterFieldToList = (lists: ListSchema[], field?: IFieldType): ListSchema[] => {
|
||||
export const filterFieldToList = (
|
||||
lists: ListSchema[],
|
||||
field?: IndexPatternFieldBase & { esTypes?: string[] }
|
||||
): ListSchema[] => {
|
||||
if (field != null) {
|
||||
const { esTypes = [] } = field;
|
||||
return lists.filter(({ type }) => esTypes.some((esType: string) => typeMatch(type, esType)));
|
||||
|
|
|
@ -31,13 +31,8 @@ describe('#getOperators', () => {
|
|||
|
||||
test('it returns "isOperator" when field type is "nested"', () => {
|
||||
const operator = getOperators({
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'nestedField',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: { nested: { path: 'nestedField' } },
|
||||
type: 'nested',
|
||||
});
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import {
|
||||
EXCEPTION_OPERATORS,
|
||||
|
@ -22,10 +20,10 @@ import {
|
|||
/**
|
||||
* Returns the appropriate operators given a field type
|
||||
*
|
||||
* @param field IFieldType selected field
|
||||
* @param field IndexPatternFieldBase selected field
|
||||
*
|
||||
*/
|
||||
export const getOperators = (field: IFieldType | undefined): OperatorOption[] => {
|
||||
export const getOperators = (field: IndexPatternFieldBase | undefined): OperatorOption[] => {
|
||||
if (field == null) {
|
||||
return [isOperator];
|
||||
} else if (field.type === 'boolean') {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '.';
|
||||
import { getField } from '../../fields/index.mock';
|
||||
import { autocompleteStartMock } from '../../autocomplete/index.mock';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
// Copied from "src/plugins/data/common/index_patterns/index_pattern.stub.ts"
|
||||
// TODO: Remove this in favor of the above if/when it is ported, https://github.com/elastic/kibana/issues/100715
|
||||
|
@ -152,6 +153,11 @@ describe('use_field_value_autocomplete', () => {
|
|||
const suggestionsMock = jest.fn().mockResolvedValue([]);
|
||||
|
||||
await act(async () => {
|
||||
const selectedField: IndexPatternFieldBase | undefined = getField('nestedField.child');
|
||||
if (selectedField == null) {
|
||||
throw new TypeError('selectedField for this test should always be defined');
|
||||
}
|
||||
|
||||
const { signal } = new AbortController();
|
||||
const { waitForNextUpdate } = renderHook<
|
||||
UseFieldValueAutocompleteProps,
|
||||
|
@ -166,7 +172,7 @@ describe('use_field_value_autocomplete', () => {
|
|||
indexPattern: stubIndexPatternWithFields,
|
||||
operatorType: OperatorTypeEnum.MATCH,
|
||||
query: '',
|
||||
selectedField: { ...getField('nestedField.child'), name: 'child' },
|
||||
selectedField: { ...selectedField, name: 'child' },
|
||||
})
|
||||
);
|
||||
// Note: initial `waitForNextUpdate` is hook initialization
|
||||
|
|
|
@ -9,19 +9,15 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715
|
||||
// import { AutocompleteStart } from '../../../../../../../../src/plugins/data/public';
|
||||
type AutocompleteStart = any;
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
type IIndexPattern = any;
|
||||
|
||||
interface FuncArgs {
|
||||
fieldSelected: IFieldType | undefined;
|
||||
patterns: IIndexPattern | undefined;
|
||||
fieldSelected: IndexPatternFieldBase | undefined;
|
||||
patterns: IndexPatternBase | undefined;
|
||||
searchQuery: string;
|
||||
value: string | string[] | undefined;
|
||||
}
|
||||
|
@ -33,10 +29,10 @@ export type UseFieldValueAutocompleteReturn = [boolean, boolean, string[], Func
|
|||
export interface UseFieldValueAutocompleteProps {
|
||||
autocompleteService: AutocompleteStart;
|
||||
fieldValue: string | string[] | undefined;
|
||||
indexPattern: IIndexPattern | undefined;
|
||||
indexPattern: IndexPatternBase | undefined;
|
||||
operatorType: OperatorTypeEnum;
|
||||
query: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
}
|
||||
/**
|
||||
* Hook for using the field value autocomplete service
|
||||
|
|
|
@ -160,13 +160,8 @@ describe('operator', () => {
|
|||
operator={isOperator}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'nestedField',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: { nested: { path: 'nestedField' } },
|
||||
type: 'nested',
|
||||
}}
|
||||
|
|
|
@ -9,10 +9,7 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { OperatorOption } from '@kbn/securitysolution-list-utils';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import { getOperators } from '../get_operators';
|
||||
import {
|
||||
|
@ -31,7 +28,7 @@ interface OperatorState {
|
|||
operatorInputWidth?: number;
|
||||
operatorOptions?: OperatorOption[];
|
||||
placeholder: string;
|
||||
selectedField: IFieldType | undefined;
|
||||
selectedField: IndexPatternFieldBase | undefined;
|
||||
}
|
||||
|
||||
export const OperatorComponent: React.FC<OperatorState> = ({
|
||||
|
|
|
@ -7,12 +7,9 @@
|
|||
*/
|
||||
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { checkEmptyValue } from '../check_empty_value';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/105731
|
||||
// import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
type IFieldType = any;
|
||||
|
||||
import * as i18n from '../translations';
|
||||
|
||||
/**
|
||||
|
@ -25,7 +22,7 @@ import * as i18n from '../translations';
|
|||
*/
|
||||
export const paramIsValid = (
|
||||
param: string | undefined,
|
||||
field: IFieldType | undefined,
|
||||
field: IndexPatternFieldBase | undefined,
|
||||
isRequired: boolean,
|
||||
touched: boolean
|
||||
): string | undefined => {
|
||||
|
|
|
@ -32,6 +32,7 @@ SRC_DEPS = [
|
|||
"//packages/kbn-securitysolution-list-constants",
|
||||
"//packages/kbn-securitysolution-io-ts-list-types",
|
||||
"//packages/kbn-securitysolution-utils",
|
||||
"//packages/kbn-es-query",
|
||||
"@npm//lodash",
|
||||
"@npm//tslib",
|
||||
]
|
||||
|
|
|
@ -21,17 +21,10 @@ import {
|
|||
entriesNested,
|
||||
OsTypeArray,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
import { hasLargeValueList } from '../has_large_value_list';
|
||||
|
||||
/**
|
||||
* Originally this was an import type of:
|
||||
* import type { Filter } from '../../../../../src/plugins/data/common';
|
||||
* TODO: Once we have the type for this within kbn packages, replace this with that one
|
||||
* @deprecated
|
||||
*/
|
||||
type Filter = any;
|
||||
|
||||
type NonListEntry = EntryMatch | EntryMatchAny | EntryNested | EntryExists;
|
||||
interface ExceptionListItemNonLargeList extends ExceptionListItemSchema {
|
||||
entries: NonListEntry[];
|
||||
|
@ -190,7 +183,7 @@ export const buildExceptionFilter = ({
|
|||
} else {
|
||||
const chunks = chunkExceptions(exceptionsWithoutLargeValueLists, chunkSize);
|
||||
|
||||
const filters = chunks.map<Filter>((exceptionsChunk) => {
|
||||
const filters = chunks.map((exceptionsChunk) => {
|
||||
const orClauses = createOrClauses(exceptionsChunk);
|
||||
|
||||
return {
|
||||
|
|
|
@ -28,11 +28,7 @@ import {
|
|||
exceptionListItemSchema,
|
||||
nestedEntryItem,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
// TODO: I have to use any here for now, but once this is available below, we should use the correct types
|
||||
// import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/public';
|
||||
type IFieldType = any;
|
||||
type IIndexPattern = any;
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import {
|
||||
EXCEPTION_OPERATORS,
|
||||
|
@ -282,17 +278,17 @@ export const getUpdatedEntriesOnDelete = (
|
|||
* add nested entry, should only show nested fields, if item is the parent
|
||||
* field of a nested entry, we only display the parent field
|
||||
*
|
||||
* @param patterns IIndexPattern containing available fields on rule index
|
||||
* @param patterns IndexPatternBase containing available fields on rule index
|
||||
* @param item exception item entry
|
||||
* set to add a nested field
|
||||
*/
|
||||
export const getFilteredIndexPatterns = (
|
||||
patterns: IIndexPattern,
|
||||
patterns: IndexPatternBase,
|
||||
item: FormattedBuilderEntry,
|
||||
type: ExceptionListType,
|
||||
preFilter?: (i: IIndexPattern, t: ExceptionListType, o?: OsTypeArray) => IIndexPattern,
|
||||
preFilter?: (i: IndexPatternBase, t: ExceptionListType, o?: OsTypeArray) => IndexPatternBase,
|
||||
osTypes?: OsTypeArray
|
||||
): IIndexPattern => {
|
||||
): IndexPatternBase => {
|
||||
const indexPatterns = preFilter != null ? preFilter(patterns, type, osTypes) : patterns;
|
||||
|
||||
if (item.nested === 'child' && item.parent != null) {
|
||||
|
@ -300,7 +296,6 @@ export const getFilteredIndexPatterns = (
|
|||
return {
|
||||
...indexPatterns,
|
||||
fields: indexPatterns.fields
|
||||
// @ts-expect-error This will go away once we type IField from any
|
||||
.filter((indexField) => {
|
||||
const fieldHasCommonParentPath =
|
||||
indexField.subType != null &&
|
||||
|
@ -310,7 +305,6 @@ export const getFilteredIndexPatterns = (
|
|||
|
||||
return fieldHasCommonParentPath;
|
||||
})
|
||||
// @ts-expect-error This will go away once we type IField from any
|
||||
.map((f) => {
|
||||
const [fieldNameWithoutParentPath] = f.name.split('.').slice(-1);
|
||||
return { ...f, name: fieldNameWithoutParentPath };
|
||||
|
@ -324,7 +318,6 @@ export const getFilteredIndexPatterns = (
|
|||
return {
|
||||
...indexPatterns,
|
||||
fields: indexPatterns.fields.filter(
|
||||
// @ts-expect-error This will go away once we type IField from any
|
||||
(field) => field.subType != null && field.subType.nested != null
|
||||
),
|
||||
};
|
||||
|
@ -342,7 +335,7 @@ export const getFilteredIndexPatterns = (
|
|||
*/
|
||||
export const getEntryOnFieldChange = (
|
||||
item: FormattedBuilderEntry,
|
||||
newField: IFieldType
|
||||
newField: IndexPatternFieldBase
|
||||
): { index: number; updatedEntry: BuilderEntry } => {
|
||||
const { parent, entryIndex, nested } = item;
|
||||
const newChildFieldValue = newField != null ? newField.name.split('.').slice(-1)[0] : '';
|
||||
|
@ -657,9 +650,9 @@ export const getCorrespondingKeywordField = ({
|
|||
fields,
|
||||
selectedField,
|
||||
}: {
|
||||
fields: IFieldType[];
|
||||
fields: IndexPatternFieldBase[];
|
||||
selectedField: string | undefined;
|
||||
}): IFieldType | undefined => {
|
||||
}): IndexPatternFieldBase | undefined => {
|
||||
const selectedFieldBits =
|
||||
selectedField != null && selectedField !== '' ? selectedField.split('.') : [];
|
||||
const selectedFieldIsTextType = selectedFieldBits.slice(-1)[0] === 'text';
|
||||
|
@ -679,7 +672,7 @@ export const getCorrespondingKeywordField = ({
|
|||
* Formats the entry into one that is easily usable for the UI, most of the
|
||||
* complexity was introduced with nested fields
|
||||
*
|
||||
* @param patterns IIndexPattern containing available fields on rule index
|
||||
* @param patterns IndexPatternBase containing available fields on rule index
|
||||
* @param item exception item entry
|
||||
* @param itemIndex entry index
|
||||
* @param parent nested entries hold copy of their parent for use in various logic
|
||||
|
@ -687,7 +680,7 @@ export const getCorrespondingKeywordField = ({
|
|||
* was added to ensure that nested items could be identified with their parent entry
|
||||
*/
|
||||
export const getFormattedBuilderEntry = (
|
||||
indexPattern: IIndexPattern,
|
||||
indexPattern: IndexPatternBase,
|
||||
item: BuilderEntry,
|
||||
itemIndex: number,
|
||||
parent: EntryNested | undefined,
|
||||
|
@ -695,7 +688,6 @@ export const getFormattedBuilderEntry = (
|
|||
): FormattedBuilderEntry => {
|
||||
const { fields } = indexPattern;
|
||||
const field = parent != null ? `${parent.field}.${item.field}` : item.field;
|
||||
// @ts-expect-error This will go away once we type IField from any
|
||||
const [foundField] = fields.filter(({ name }) => field != null && field === name);
|
||||
const correspondingKeywordField = getCorrespondingKeywordField({
|
||||
fields,
|
||||
|
@ -734,7 +726,7 @@ export const getFormattedBuilderEntry = (
|
|||
* Formats the entries to be easily usable for the UI, most of the
|
||||
* complexity was introduced with nested fields
|
||||
*
|
||||
* @param patterns IIndexPattern containing available fields on rule index
|
||||
* @param patterns IndexPatternBase containing available fields on rule index
|
||||
* @param entries exception item entries
|
||||
* @param addNested boolean noting whether or not UI is currently
|
||||
* set to add a nested field
|
||||
|
@ -743,7 +735,7 @@ export const getFormattedBuilderEntry = (
|
|||
* was added to ensure that nested items could be identified with their parent entry
|
||||
*/
|
||||
export const getFormattedBuilderEntries = (
|
||||
indexPattern: IIndexPattern,
|
||||
indexPattern: IndexPatternBase,
|
||||
entries: BuilderEntry[],
|
||||
parent?: EntryNested,
|
||||
parentIndex?: number
|
||||
|
@ -765,13 +757,14 @@ export const getFormattedBuilderEntries = (
|
|||
entryIndex: index,
|
||||
field: isNewNestedEntry
|
||||
? undefined
|
||||
: {
|
||||
: // This type below is really a FieldSpec type from "src/plugins/data/common/index_patterns/fields/types.ts", we cast it here to keep using the IndexPatternFieldBase interface
|
||||
({
|
||||
aggregatable: false,
|
||||
esTypes: ['nested'],
|
||||
name: item.field != null ? item.field : '',
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
},
|
||||
} as IndexPatternFieldBase),
|
||||
id: item.id != null ? item.id : `${index}`,
|
||||
nested: 'parent',
|
||||
operator: isOperator,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import type {
|
||||
CreateExceptionListItemSchema,
|
||||
Entry,
|
||||
|
@ -30,21 +31,15 @@ export interface OperatorOption {
|
|||
type: OperatorTypeEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the one from core once it is in its own package which will be from:
|
||||
* Original import was // import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
*/
|
||||
type IFieldType = any;
|
||||
|
||||
export interface FormattedBuilderEntry {
|
||||
id: string;
|
||||
field: IFieldType | undefined;
|
||||
field: IndexPatternFieldBase | undefined;
|
||||
operator: OperatorOption;
|
||||
value: string | string[] | undefined;
|
||||
nested: 'parent' | 'child' | undefined;
|
||||
entryIndex: number;
|
||||
parent: { parent: BuilderEntryNested; parentIndex: number } | undefined;
|
||||
correspondingKeywordField: IFieldType | undefined;
|
||||
correspondingKeywordField: IndexPatternFieldBase | undefined;
|
||||
}
|
||||
|
||||
export interface EmptyEntry {
|
||||
|
|
|
@ -119,7 +119,7 @@ export default {
|
|||
},
|
||||
indexPatterns: {
|
||||
description:
|
||||
'`IIndexPattern` - index patterns used to populate field options and value autocomplete.',
|
||||
'`IndexPatternBase` - index patterns used to populate field options and value autocomplete.',
|
||||
type: {
|
||||
required: true,
|
||||
},
|
||||
|
@ -201,7 +201,7 @@ export default {
|
|||
},
|
||||
listTypeSpecificIndexPatternFilter: {
|
||||
description:
|
||||
'`(pattern: IIndexPattern, type: ExceptionListType) => IIndexPattern` - callback invoked when index patterns filtered. Optional to be used if you would only like certain fields displayed.',
|
||||
'`(pattern: IndexPatternBase, type: ExceptionListType) => IndexPatternBase` - callback invoked when index patterns filtered. Optional to be used if you would only like certain fields displayed.',
|
||||
type: {
|
||||
required: false,
|
||||
},
|
||||
|
|
|
@ -102,7 +102,7 @@ export default {
|
|||
},
|
||||
indexPattern: {
|
||||
description:
|
||||
'`IIndexPattern` - index patterns used to populate field options and value autocomplete.',
|
||||
'`IndexPatternBase` - index patterns used to populate field options and value autocomplete.',
|
||||
type: {
|
||||
required: true,
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
isOperator,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { useFindLists } from '@kbn/securitysolution-list-hooks';
|
||||
import { FieldSpec } from 'src/plugins/data/common';
|
||||
|
||||
import {
|
||||
fields,
|
||||
|
@ -374,31 +375,33 @@ describe('BuilderEntryItem', () => {
|
|||
});
|
||||
|
||||
test('it uses "correspondingKeywordField" if it exists', () => {
|
||||
const correspondingKeywordField: FieldSpec = {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'extension',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
};
|
||||
const field: FieldSpec = {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'extension.text',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
};
|
||||
wrapper = mount(
|
||||
<BuilderEntryItem
|
||||
autocompleteService={autocompleteStartMock}
|
||||
entry={{
|
||||
correspondingKeywordField: {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'extension',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
correspondingKeywordField,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'extension.text',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
},
|
||||
field,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOneOfOperator,
|
||||
|
@ -538,10 +541,10 @@ describe('BuilderEntryItem', () => {
|
|||
|
||||
((wrapper.find(EuiComboBox).at(2).props() as unknown) as {
|
||||
onCreateOption: (a: string) => void;
|
||||
}).onCreateOption('126.45.211.34');
|
||||
}).onCreateOption('127.0.0.1');
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
{ field: 'ip', id: '123', operator: 'excluded', type: 'match', value: '126.45.211.34' },
|
||||
{ field: 'ip', id: '123', operator: 'excluded', type: 'match', value: '127.0.0.1' },
|
||||
0
|
||||
);
|
||||
});
|
||||
|
@ -576,10 +579,10 @@ describe('BuilderEntryItem', () => {
|
|||
|
||||
((wrapper.find(EuiComboBox).at(2).props() as unknown) as {
|
||||
onCreateOption: (a: string) => void;
|
||||
}).onCreateOption('126.45.211.34');
|
||||
}).onCreateOption('127.0.0.1');
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
{ field: 'ip', id: '123', operator: 'included', type: 'match_any', value: ['126.45.211.34'] },
|
||||
{ field: 'ip', id: '123', operator: 'included', type: 'match_any', value: ['127.0.0.1'] },
|
||||
0
|
||||
);
|
||||
});
|
||||
|
|
|
@ -35,9 +35,9 @@ import {
|
|||
FieldComponent,
|
||||
OperatorComponent,
|
||||
} from '@kbn/securitysolution-autocomplete';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import { AutocompleteStart } from '../../../../../../../src/plugins/data/public';
|
||||
import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { HttpStart } from '../../../../../../../src/core/public';
|
||||
import { getEmptyValue } from '../../../common/empty_value';
|
||||
|
||||
|
@ -52,15 +52,15 @@ export interface EntryItemProps {
|
|||
autocompleteService: AutocompleteStart;
|
||||
entry: FormattedBuilderEntry;
|
||||
httpService: HttpStart;
|
||||
indexPattern: IIndexPattern;
|
||||
indexPattern: IndexPatternBase;
|
||||
showLabel: boolean;
|
||||
osTypes?: OsTypeArray;
|
||||
listType: ExceptionListType;
|
||||
listTypeSpecificIndexPatternFilter?: (
|
||||
pattern: IIndexPattern,
|
||||
pattern: IndexPatternBase,
|
||||
type: ExceptionListType,
|
||||
osTypes?: OsTypeArray
|
||||
) => IIndexPattern;
|
||||
) => IndexPatternBase;
|
||||
onChange: (arg: BuilderEntry, i: number) => void;
|
||||
onlyShowListOperators?: boolean;
|
||||
setErrorsExist: (arg: boolean) => void;
|
||||
|
@ -90,7 +90,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
([newField]: IFieldType[]): void => {
|
||||
([newField]: IndexPatternFieldBase[]): void => {
|
||||
const { updatedEntry, index } = getEntryOnFieldChange(entry, newField);
|
||||
onChange(updatedEntry, index);
|
||||
},
|
||||
|
|
|
@ -18,8 +18,7 @@ import {
|
|||
getFormattedBuilderEntries,
|
||||
getUpdatedEntriesOnDelete,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
|
||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { IndexPatternBase } from '@kbn/es-query';
|
||||
|
||||
import { BuilderAndBadgeComponent } from './and_badge';
|
||||
import { BuilderEntryDeleteButtonComponent } from './entry_delete_button';
|
||||
|
@ -47,15 +46,15 @@ interface BuilderExceptionListItemProps {
|
|||
exceptionItem: ExceptionsBuilderExceptionItem;
|
||||
exceptionItemIndex: number;
|
||||
osTypes?: OsTypeArray;
|
||||
indexPattern: IIndexPattern;
|
||||
indexPattern: IndexPatternBase;
|
||||
andLogicIncluded: boolean;
|
||||
isOnlyItem: boolean;
|
||||
listType: ExceptionListType;
|
||||
listTypeSpecificIndexPatternFilter?: (
|
||||
pattern: IIndexPattern,
|
||||
pattern: IndexPatternBase,
|
||||
type: ExceptionListType,
|
||||
osTypes?: OsTypeArray
|
||||
) => IIndexPattern;
|
||||
) => IndexPatternBase;
|
||||
onDeleteExceptionItem: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
||||
onChangeExceptionItem: (item: ExceptionsBuilderExceptionItem, index: number) => void;
|
||||
setErrorsExist: (arg: boolean) => void;
|
||||
|
|
|
@ -30,8 +30,9 @@ import {
|
|||
getDefaultNestedEmptyEntry,
|
||||
getNewExceptionItem,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { IndexPatternBase } from '@kbn/es-query';
|
||||
|
||||
import { AutocompleteStart, IIndexPattern } from '../../../../../../../src/plugins/data/public';
|
||||
import { AutocompleteStart } from '../../../../../../../src/plugins/data/public';
|
||||
import { AndOrBadge } from '../and_or_badge';
|
||||
|
||||
import { BuilderExceptionListItemComponent } from './exception_item_renderer';
|
||||
|
@ -75,7 +76,7 @@ export interface ExceptionBuilderProps {
|
|||
exceptionListItems: ExceptionsBuilderExceptionItem[];
|
||||
httpService: HttpStart;
|
||||
osTypes?: OsTypeArray;
|
||||
indexPatterns: IIndexPattern;
|
||||
indexPatterns: IndexPatternBase;
|
||||
isAndDisabled: boolean;
|
||||
isNestedDisabled: boolean;
|
||||
isOrDisabled: boolean;
|
||||
|
@ -83,9 +84,9 @@ export interface ExceptionBuilderProps {
|
|||
listNamespaceType: NamespaceType;
|
||||
listType: ExceptionListType;
|
||||
listTypeSpecificIndexPatternFilter?: (
|
||||
pattern: IIndexPattern,
|
||||
pattern: IndexPatternBase,
|
||||
type: ExceptionListType
|
||||
) => IIndexPattern;
|
||||
) => IndexPatternBase;
|
||||
onChange: (arg: OnChangeProps) => void;
|
||||
ruleName: string;
|
||||
isDisabled?: boolean;
|
||||
|
|
|
@ -52,6 +52,7 @@ import {
|
|||
isOneOfOperator,
|
||||
isOperator,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
|
||||
import { ENTRIES_WITH_IDS } from '../../../../common/constants.mock';
|
||||
import { getEntryExistsMock } from '../../../../common/schemas/types/entry_exists.mock';
|
||||
|
@ -60,7 +61,7 @@ import {
|
|||
fields,
|
||||
getField,
|
||||
} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
|
||||
import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { FieldSpec } from '../../../../../../../src/plugins/data/common';
|
||||
import { getEntryNestedMock } from '../../../../common/schemas/types/entry_nested.mock';
|
||||
import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock';
|
||||
import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock';
|
||||
|
@ -93,7 +94,7 @@ const getEntryMatchAnyWithIdMock = (): EntryMatchAny & { id: string } => ({
|
|||
id: '123',
|
||||
});
|
||||
|
||||
const getMockIndexPattern = (): IIndexPattern => ({
|
||||
const getMockIndexPattern = (): IndexPatternBase => ({
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -131,7 +132,11 @@ const getMockNestedBuilderEntry = (): FormattedBuilderEntry => ({
|
|||
const getMockNestedParentBuilderEntry = (): FormattedBuilderEntry => ({
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: { ...getField('nestedField.child'), esTypes: ['nested'], name: 'nestedField' },
|
||||
field: {
|
||||
...getField('nestedField.child'),
|
||||
esTypes: ['nested'],
|
||||
name: 'nestedField',
|
||||
} as FieldSpec,
|
||||
id: '123',
|
||||
nested: 'parent',
|
||||
operator: isOperator,
|
||||
|
@ -163,10 +168,13 @@ const mockEndpointFields = [
|
|||
},
|
||||
];
|
||||
|
||||
export const getEndpointField = (name: string): IFieldType =>
|
||||
mockEndpointFields.find((field) => field.name === name) as IFieldType;
|
||||
export const getEndpointField = (name: string): IndexPatternFieldBase =>
|
||||
mockEndpointFields.find((field) => field.name === name) as IndexPatternFieldBase;
|
||||
|
||||
const filterIndexPatterns = (patterns: IIndexPattern, type: ExceptionListType): IIndexPattern => {
|
||||
const filterIndexPatterns = (
|
||||
patterns: IndexPatternBase,
|
||||
type: ExceptionListType
|
||||
): IndexPatternBase => {
|
||||
return type === 'endpoint'
|
||||
? {
|
||||
...patterns,
|
||||
|
@ -181,10 +189,10 @@ describe('Exception builder helpers', () => {
|
|||
describe('#getFilteredIndexPatterns', () => {
|
||||
describe('list type detections', () => {
|
||||
test('it returns nested fields that match parent value when "item.nested" is "child"', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: FormattedBuilderEntry = getMockNestedBuilderEntry();
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase = {
|
||||
fields: [{ ...getField('nestedField.child'), name: 'child' }],
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -193,10 +201,10 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns only parent nested field when "item.nested" is "parent" and nested parent field is not undefined', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: FormattedBuilderEntry = getMockNestedParentBuilderEntry();
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase & { fields: Array<Partial<FieldSpec>> } = {
|
||||
fields: [{ ...getField('nestedField.child'), esTypes: ['nested'], name: 'nestedField' }],
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -205,13 +213,13 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns only nested fields when "item.nested" is "parent" and nested parent field is undefined', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: FormattedBuilderEntry = {
|
||||
...getMockNestedParentBuilderEntry(),
|
||||
field: undefined,
|
||||
};
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase = {
|
||||
fields: [
|
||||
{ ...getField('nestedField.child') },
|
||||
{ ...getField('nestedField.nestedChild.doublyNestedChild') },
|
||||
|
@ -223,10 +231,10 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns all fields unfiletered if "item.nested" is not "child" or "parent"', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase = {
|
||||
fields: [...fields],
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -236,7 +244,7 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
describe('list type endpoint', () => {
|
||||
let payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
let payloadIndexPattern = getMockIndexPattern();
|
||||
|
||||
beforeAll(() => {
|
||||
payloadIndexPattern = {
|
||||
|
@ -264,7 +272,7 @@ describe('Exception builder helpers', () => {
|
|||
value: 'some value',
|
||||
};
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'endpoint');
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase = {
|
||||
fields: [{ ...getEndpointField('file.Ext.code_signature.status'), name: 'status' }],
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -273,33 +281,35 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns only parent nested field when "item.nested" is "parent" and nested parent field is not undefined', () => {
|
||||
const field: FieldSpec = {
|
||||
...getEndpointField('file.Ext.code_signature.status'),
|
||||
esTypes: ['nested'],
|
||||
name: 'file.Ext.code_signature',
|
||||
} as FieldSpec;
|
||||
const payloadItem: FormattedBuilderEntry = {
|
||||
...getMockNestedParentBuilderEntry(),
|
||||
field: {
|
||||
...getEndpointField('file.Ext.code_signature.status'),
|
||||
esTypes: ['nested'],
|
||||
name: 'file.Ext.code_signature',
|
||||
},
|
||||
field,
|
||||
};
|
||||
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'endpoint');
|
||||
const expected: IIndexPattern = {
|
||||
fields: [
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['nested'],
|
||||
name: 'file.Ext.code_signature',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'file.Ext.code_signature',
|
||||
},
|
||||
const fieldsExpected: FieldSpec[] = [
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['nested'],
|
||||
name: 'file.Ext.code_signature',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'file.Ext.code_signature',
|
||||
},
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
const expected: IndexPatternBase = {
|
||||
fields: fieldsExpected,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
};
|
||||
|
@ -317,7 +327,7 @@ describe('Exception builder helpers', () => {
|
|||
'endpoint',
|
||||
filterIndexPatterns
|
||||
);
|
||||
const expected: IIndexPattern = {
|
||||
const expected: IndexPatternBase = {
|
||||
fields: [getEndpointField('file.Ext.code_signature.status')],
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -333,30 +343,31 @@ describe('Exception builder helpers', () => {
|
|||
'endpoint',
|
||||
filterIndexPatterns
|
||||
);
|
||||
const expected: IIndexPattern = {
|
||||
fields: [
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'file.path.caseless',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'file.Ext.code_signature.status',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: { nested: { path: 'file.Ext.code_signature' } },
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
const fieldsExpected: FieldSpec[] = [
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'file.path.caseless',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'file.Ext.code_signature.status',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: { nested: { path: 'file.Ext.code_signature' } },
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
const expected: IndexPatternBase = {
|
||||
fields: fieldsExpected,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
};
|
||||
|
@ -626,7 +637,7 @@ describe('Exception builder helpers', () => {
|
|||
describe('#getEntryOnFieldChange', () => {
|
||||
test('it returns nested entry with single new subentry when "item.nested" is "parent"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockNestedParentBuilderEntry();
|
||||
const payloadIFieldType: IFieldType = getField('nestedField.child');
|
||||
const payloadIFieldType = getField('nestedField.child');
|
||||
const output = getEntryOnFieldChange(payloadItem, payloadIFieldType);
|
||||
const expected: { updatedEntry: BuilderEntry & { id?: string }; index: number } = {
|
||||
index: 0,
|
||||
|
@ -663,7 +674,7 @@ describe('Exception builder helpers', () => {
|
|||
parentIndex: 0,
|
||||
},
|
||||
};
|
||||
const payloadIFieldType: IFieldType = getField('nestedField.child');
|
||||
const payloadIFieldType = getField('nestedField.child');
|
||||
const output = getEntryOnFieldChange(payloadItem, payloadIFieldType);
|
||||
const expected: { updatedEntry: BuilderEntry & { id?: string }; index: number } = {
|
||||
index: 0,
|
||||
|
@ -688,7 +699,7 @@ describe('Exception builder helpers', () => {
|
|||
|
||||
test('it returns field of type "match" with updated field if not a nested entry', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const payloadIFieldType: IFieldType = getField('ip');
|
||||
const payloadIFieldType = getField('ip');
|
||||
const output = getEntryOnFieldChange(payloadItem, payloadIFieldType);
|
||||
const expected: { updatedEntry: BuilderEntry & { id?: string }; index: number } = {
|
||||
index: 0,
|
||||
|
@ -1023,7 +1034,7 @@ describe('Exception builder helpers', () => {
|
|||
|
||||
describe('#getFormattedBuilderEntries', () => {
|
||||
test('it returns formatted entry with field undefined if it unable to find a matching index pattern field', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItems: BuilderEntry[] = [getEntryMatchWithIdMock()];
|
||||
const output = getFormattedBuilderEntries(payloadIndexPattern, payloadItems);
|
||||
const expected: FormattedBuilderEntry[] = [
|
||||
|
@ -1042,26 +1053,37 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns formatted entries when no nested entries exist', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItems: BuilderEntry[] = [
|
||||
{ ...getEntryMatchWithIdMock(), field: 'ip', value: 'some ip' },
|
||||
{ ...getEntryMatchAnyWithIdMock(), field: 'extension', value: ['some extension'] },
|
||||
];
|
||||
const output = getFormattedBuilderEntries(payloadIndexPattern, payloadItems);
|
||||
const field1: FieldSpec = {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
};
|
||||
const field2: FieldSpec = {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'extension',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
};
|
||||
const expected: FormattedBuilderEntry[] = [
|
||||
{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
},
|
||||
field: field1,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOperator,
|
||||
|
@ -1071,16 +1093,7 @@ describe('Exception builder helpers', () => {
|
|||
{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 1,
|
||||
field: {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['keyword'],
|
||||
name: 'extension',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
field: field2,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOneOfOperator,
|
||||
|
@ -1092,7 +1105,7 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns formatted entries when nested entries exist', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadParent: EntryNested = {
|
||||
...getEntryNestedWithIdMock(),
|
||||
entries: [{ ...getEntryMatchWithIdMock(), field: 'child' }],
|
||||
|
@ -1104,20 +1117,43 @@ describe('Exception builder helpers', () => {
|
|||
];
|
||||
|
||||
const output = getFormattedBuilderEntries(payloadIndexPattern, payloadItems);
|
||||
const field1: FieldSpec = {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
};
|
||||
const field2: FieldSpec = {
|
||||
aggregatable: false,
|
||||
esTypes: ['nested'],
|
||||
name: 'nestedField',
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
};
|
||||
const field3: FieldSpec = {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'child',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
};
|
||||
const expected: FormattedBuilderEntry[] = [
|
||||
{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
},
|
||||
field: field1,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOperator,
|
||||
|
@ -1127,13 +1163,7 @@ describe('Exception builder helpers', () => {
|
|||
{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 1,
|
||||
field: {
|
||||
aggregatable: false,
|
||||
esTypes: ['nested'],
|
||||
name: 'nestedField',
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
},
|
||||
field: field2,
|
||||
id: '123',
|
||||
nested: 'parent',
|
||||
operator: isOperator,
|
||||
|
@ -1143,21 +1173,7 @@ describe('Exception builder helpers', () => {
|
|||
{
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'child',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
},
|
||||
field: field3,
|
||||
id: '123',
|
||||
nested: 'child',
|
||||
operator: isOperator,
|
||||
|
@ -1248,7 +1264,7 @@ describe('Exception builder helpers', () => {
|
|||
|
||||
describe('#getFormattedBuilderEntry', () => {
|
||||
test('it returns entry with a value for "correspondingKeywordField" when "item.field" is of type "text" and matching keyword field exists', () => {
|
||||
const payloadIndexPattern: IIndexPattern = {
|
||||
const payloadIndexPattern: IndexPatternBase = {
|
||||
...getMockIndexPattern(),
|
||||
fields: [
|
||||
...fields,
|
||||
|
@ -1276,19 +1292,20 @@ describe('Exception builder helpers', () => {
|
|||
undefined,
|
||||
undefined
|
||||
);
|
||||
const field: FieldSpec = {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'machine.os.raw.text',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
};
|
||||
const expected: FormattedBuilderEntry = {
|
||||
correspondingKeywordField: getField('machine.os.raw'),
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'machine.os.raw.text',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: false,
|
||||
type: 'string',
|
||||
},
|
||||
field,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOperator,
|
||||
|
@ -1299,7 +1316,7 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns "FormattedBuilderEntry" with value "nested" of "child" when "parent" and "parentIndex" are defined', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: BuilderEntry = { ...getEntryMatchWithIdMock(), field: 'child' };
|
||||
const payloadParent: EntryNested = {
|
||||
...getEntryNestedWithIdMock(),
|
||||
|
@ -1313,24 +1330,25 @@ describe('Exception builder helpers', () => {
|
|||
payloadParent,
|
||||
1
|
||||
);
|
||||
const field: FieldSpec = {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'child',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
};
|
||||
const expected: FormattedBuilderEntry = {
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: false,
|
||||
count: 0,
|
||||
esTypes: ['text'],
|
||||
name: 'child',
|
||||
readFromDocValues: false,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
subType: {
|
||||
nested: {
|
||||
path: 'nestedField',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
},
|
||||
field,
|
||||
id: '123',
|
||||
nested: 'child',
|
||||
operator: isOperator,
|
||||
|
@ -1349,7 +1367,7 @@ describe('Exception builder helpers', () => {
|
|||
});
|
||||
|
||||
test('it returns non nested "FormattedBuilderEntry" when "parent" and "parentIndex" are not defined', () => {
|
||||
const payloadIndexPattern: IIndexPattern = getMockIndexPattern();
|
||||
const payloadIndexPattern = getMockIndexPattern();
|
||||
const payloadItem: BuilderEntry = {
|
||||
...getEntryMatchWithIdMock(),
|
||||
field: 'ip',
|
||||
|
@ -1362,19 +1380,20 @@ describe('Exception builder helpers', () => {
|
|||
undefined,
|
||||
undefined
|
||||
);
|
||||
const field: FieldSpec = {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
};
|
||||
const expected: FormattedBuilderEntry = {
|
||||
correspondingKeywordField: undefined,
|
||||
entryIndex: 0,
|
||||
field: {
|
||||
aggregatable: true,
|
||||
count: 0,
|
||||
esTypes: ['ip'],
|
||||
name: 'ip',
|
||||
readFromDocValues: true,
|
||||
scripted: false,
|
||||
searchable: true,
|
||||
type: 'ip',
|
||||
},
|
||||
field,
|
||||
id: '123',
|
||||
nested: undefined,
|
||||
operator: isOperator,
|
||||
|
|
|
@ -41,14 +41,14 @@ import { getEntryMatchMock } from '../../../../../lists/common/schemas/types/ent
|
|||
import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comment.mock';
|
||||
import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
|
||||
import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock';
|
||||
import { IFieldType, IIndexPattern } from 'src/plugins/data/common';
|
||||
import { CodeSignature } from '../../../../common/ecs/file';
|
||||
import { IndexPatternBase } from '@kbn/es-query';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn().mockReturnValue('123'),
|
||||
}));
|
||||
|
||||
const getMockIndexPattern = (): IIndexPattern => ({
|
||||
const getMockIndexPattern = (): IndexPatternBase => ({
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
|
@ -91,9 +91,6 @@ const mockLinuxEndpointFields = [
|
|||
},
|
||||
];
|
||||
|
||||
export const getEndpointField = (name: string) =>
|
||||
mockEndpointFields.find((field) => field.name === name) as IFieldType;
|
||||
|
||||
describe('Exception helpers', () => {
|
||||
beforeEach(() => {
|
||||
moment.tz.setDefault('UTC');
|
||||
|
@ -367,7 +364,7 @@ describe('Exception helpers', () => {
|
|||
name: 'nested.field',
|
||||
},
|
||||
],
|
||||
} as IIndexPattern;
|
||||
} as IndexPatternBase;
|
||||
|
||||
test('it should return false with an empty array', () => {
|
||||
const payload: ExceptionListItemSchema[] = [];
|
||||
|
|
|
@ -33,10 +33,10 @@ import {
|
|||
addIdToEntries,
|
||||
ExceptionsBuilderExceptionItem,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { IndexPatternBase } from '@kbn/es-query';
|
||||
import * as i18n from './translations';
|
||||
import { AlertData, Flattened } from './types';
|
||||
|
||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { Ecs } from '../../../../common/ecs';
|
||||
import { CodeSignature } from '../../../../common/ecs/file';
|
||||
import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard';
|
||||
|
@ -46,10 +46,10 @@ import exceptionableEndpointFields from './exceptionable_endpoint_fields.json';
|
|||
import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json';
|
||||
|
||||
export const filterIndexPatterns = (
|
||||
patterns: IIndexPattern,
|
||||
patterns: IndexPatternBase,
|
||||
type: ExceptionListType,
|
||||
osTypes?: OsTypeArray
|
||||
): IIndexPattern => {
|
||||
): IndexPatternBase => {
|
||||
switch (type) {
|
||||
case 'endpoint':
|
||||
const osFilterForEndpoint: (name: string) => boolean = osTypes?.includes('linux')
|
||||
|
@ -634,7 +634,7 @@ export const getPrepopulatedMemoryShellcodeException = ({
|
|||
*/
|
||||
export const entryHasNonEcsType = (
|
||||
exceptionItems: Array<ExceptionListItemSchema | CreateExceptionListItemSchema>,
|
||||
indexPatterns: IIndexPattern
|
||||
indexPatterns: IndexPatternBase
|
||||
): boolean => {
|
||||
const doesFieldNameExist = (exceptionEntry: Entry): boolean => {
|
||||
return indexPatterns.fields.some(({ name }) => name === exceptionEntry.field);
|
||||
|
|
|
@ -10,7 +10,8 @@ import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import styled from 'styled-components';
|
||||
|
||||
import { FieldComponent } from '@kbn/securitysolution-autocomplete';
|
||||
import { IFieldType, IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { FormattedEntry, Entry } from './types';
|
||||
import * as i18n from './translations';
|
||||
import { getEntryOnFieldChange, getEntryOnThreatFieldChange } from './helpers';
|
||||
|
@ -40,7 +41,7 @@ export const EntryItem: React.FC<EntryItemProps> = ({
|
|||
onChange,
|
||||
}): JSX.Element => {
|
||||
const handleFieldChange = useCallback(
|
||||
([newField]: IFieldType[]): void => {
|
||||
([newField]: IndexPatternFieldBase[]): void => {
|
||||
const { updatedEntry, index } = getEntryOnFieldChange(entry, newField);
|
||||
onChange(updatedEntry, index);
|
||||
},
|
||||
|
@ -48,7 +49,7 @@ export const EntryItem: React.FC<EntryItemProps> = ({
|
|||
);
|
||||
|
||||
const handleThreatFieldChange = useCallback(
|
||||
([newField]: IFieldType[]): void => {
|
||||
([newField]: IndexPatternFieldBase[]): void => {
|
||||
const { updatedEntry, index } = getEntryOnThreatFieldChange(entry, newField);
|
||||
onChange(updatedEntry, index);
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
getField,
|
||||
} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
|
||||
import { Entry, EmptyEntry, ThreatMapEntries, FormattedEntry } from './types';
|
||||
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { FieldSpec, IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import {
|
||||
|
@ -88,7 +88,7 @@ describe('Helpers', () => {
|
|||
searchable: false,
|
||||
aggregatable: false,
|
||||
readFromDocValues: true,
|
||||
},
|
||||
} as FieldSpec,
|
||||
type: 'mapping',
|
||||
value: undefined,
|
||||
};
|
||||
|
@ -130,7 +130,7 @@ describe('Helpers', () => {
|
|||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
} as FieldSpec,
|
||||
value: undefined,
|
||||
type: 'mapping',
|
||||
},
|
||||
|
@ -156,7 +156,7 @@ describe('Helpers', () => {
|
|||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
} as FieldSpec,
|
||||
value: {
|
||||
name: 'machine.os',
|
||||
type: 'string',
|
||||
|
@ -166,7 +166,7 @@ describe('Helpers', () => {
|
|||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
} as FieldSpec,
|
||||
type: 'mapping',
|
||||
},
|
||||
];
|
||||
|
@ -192,7 +192,7 @@ describe('Helpers', () => {
|
|||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
} as FieldSpec,
|
||||
type: 'mapping',
|
||||
value: {
|
||||
name: 'machine.os',
|
||||
|
@ -203,7 +203,7 @@ describe('Helpers', () => {
|
|||
searchable: true,
|
||||
aggregatable: true,
|
||||
readFromDocValues: false,
|
||||
},
|
||||
} as FieldSpec,
|
||||
entryIndex: 0,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -10,7 +10,8 @@ import { i18n } from '@kbn/i18n';
|
|||
import { addIdToItem } from '@kbn/securitysolution-utils';
|
||||
import { ThreatMap, threatMap, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
import { IndexPattern, IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { IndexPattern } from '../../../../../../../src/plugins/data/common';
|
||||
import { Entry, FormattedEntry, ThreatMapEntries, EmptyEntry } from './types';
|
||||
import { ValidationFunc } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
import { ERROR_CODE } from '../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types';
|
||||
|
@ -90,7 +91,7 @@ export const getUpdatedEntriesOnDelete = (
|
|||
*/
|
||||
export const getEntryOnFieldChange = (
|
||||
item: FormattedEntry,
|
||||
newField: IFieldType
|
||||
newField: IndexPatternFieldBase
|
||||
): { updatedEntry: Entry; index: number } => {
|
||||
const { entryIndex } = item;
|
||||
return {
|
||||
|
@ -113,7 +114,7 @@ export const getEntryOnFieldChange = (
|
|||
*/
|
||||
export const getEntryOnThreatFieldChange = (
|
||||
item: FormattedEntry,
|
||||
newField: IFieldType
|
||||
newField: IndexPatternFieldBase
|
||||
): { updatedEntry: Entry; index: number } => {
|
||||
const { entryIndex } = item;
|
||||
return {
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { ThreatMap, ThreatMapEntry } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { IFieldType } from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
export interface FormattedEntry {
|
||||
id: string;
|
||||
field: IFieldType | undefined;
|
||||
field: IndexPatternFieldBase | undefined;
|
||||
type: 'mapping';
|
||||
value: IFieldType | undefined;
|
||||
value: IndexPatternFieldBase | undefined;
|
||||
entryIndex: number;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,14 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiFormRow } from '@elastic/eui';
|
||||
import { FieldComponent } from '@kbn/securitysolution-autocomplete';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields';
|
||||
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common';
|
||||
|
||||
interface AutocompleteFieldProps {
|
||||
dataTestSubj: string;
|
||||
field: FieldHook;
|
||||
idAria: string;
|
||||
indices: IIndexPattern;
|
||||
indices: IndexPatternBase;
|
||||
isDisabled: boolean;
|
||||
fieldType: string;
|
||||
placeholder?: string;
|
||||
|
@ -32,7 +31,7 @@ export const AutocompleteField = ({
|
|||
placeholder,
|
||||
}: AutocompleteFieldProps) => {
|
||||
const handleFieldChange = useCallback(
|
||||
([newField]: IFieldType[]): void => {
|
||||
([newField]: IndexPatternFieldBase[]): void => {
|
||||
// TODO: Update onChange type in FieldComponent as newField can be undefined
|
||||
field.setValue(newField?.name ?? '');
|
||||
},
|
||||
|
|
|
@ -21,11 +21,10 @@ import styled from 'styled-components';
|
|||
import { noop } from 'lodash/fp';
|
||||
import { RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { FieldComponent } from '@kbn/securitysolution-autocomplete';
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import * as i18n from './translations';
|
||||
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
import { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types';
|
||||
import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields';
|
||||
import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns';
|
||||
|
||||
const NestedContent = styled.div`
|
||||
margin-left: 24px;
|
||||
|
@ -47,7 +46,7 @@ interface RiskScoreFieldProps {
|
|||
dataTestSubj: string;
|
||||
field: FieldHook<AboutStepRiskScore>;
|
||||
idAria: string;
|
||||
indices: IIndexPattern;
|
||||
indices: IndexPatternBase;
|
||||
isDisabled: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
@ -79,7 +78,7 @@ export const RiskScoreField = ({
|
|||
);
|
||||
|
||||
const handleRiskScoreMappingChange = useCallback(
|
||||
([newField]: IFieldType[]): void => {
|
||||
([newField]: IndexPatternFieldBase[]): void => {
|
||||
setValue({
|
||||
value,
|
||||
isMappingChecked,
|
||||
|
@ -232,14 +231,17 @@ export const RiskScoreField = ({
|
|||
};
|
||||
|
||||
/**
|
||||
* Looks for field metadata (IFieldType) in existing index pattern.
|
||||
* If specified field doesn't exist, returns a stub IFieldType created based on the mapping --
|
||||
* Looks for field metadata (IndexPatternFieldBase) in existing index pattern.
|
||||
* If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping --
|
||||
* because the field might not have been indexed yet, but we still need to display the mapping.
|
||||
*
|
||||
* @param mapping Mapping of a specified field name to risk score.
|
||||
* @param pattern Existing index pattern.
|
||||
*/
|
||||
const getFieldTypeByMapping = (mapping: RiskScoreMapping, pattern: IIndexPattern): IFieldType => {
|
||||
const getFieldTypeByMapping = (
|
||||
mapping: RiskScoreMapping,
|
||||
pattern: IndexPatternBase
|
||||
): IndexPatternFieldBase => {
|
||||
const field = mapping?.[0]?.field ?? '';
|
||||
const [knownFieldType] = pattern.fields.filter(({ name }) => field != null && field === name);
|
||||
return knownFieldType ?? { name: field, type: 'number' };
|
||||
|
|
|
@ -29,14 +29,11 @@ import {
|
|||
AutocompleteFieldMatchComponent,
|
||||
} from '@kbn/securitysolution-autocomplete';
|
||||
|
||||
import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query';
|
||||
import * as i18n from './translations';
|
||||
import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
import { SeverityOptionItem } from '../step_about_rule/data';
|
||||
import { AboutStepSeverity } from '../../../pages/detection_engine/rules/types';
|
||||
import {
|
||||
IFieldType,
|
||||
IIndexPattern,
|
||||
} from '../../../../../../../../src/plugins/data/common/index_patterns';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
const NestedContent = styled.div`
|
||||
|
@ -59,7 +56,7 @@ interface SeverityFieldProps {
|
|||
dataTestSubj: string;
|
||||
field: FieldHook<AboutStepSeverity>;
|
||||
idAria: string;
|
||||
indices: IIndexPattern;
|
||||
indices: IndexPatternBase;
|
||||
isDisabled: boolean;
|
||||
options: SeverityOptionItem[];
|
||||
}
|
||||
|
@ -88,7 +85,7 @@ export const SeverityField = ({
|
|||
);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(index: number, severity: Severity, [newField]: IFieldType[]): void => {
|
||||
(index: number, severity: Severity, [newField]: IndexPatternFieldBase[]): void => {
|
||||
const newMappingItems: SeverityMapping = [
|
||||
{
|
||||
...mapping[index],
|
||||
|
@ -298,8 +295,8 @@ export const SeverityField = ({
|
|||
};
|
||||
|
||||
/**
|
||||
* Looks for field metadata (IFieldType) in existing index pattern.
|
||||
* If specified field doesn't exist, returns a stub IFieldType created based on the mapping --
|
||||
* Looks for field metadata (IndexPatternFieldBase) in existing index pattern.
|
||||
* If specified field doesn't exist, returns a stub IndexPatternFieldBase created based on the mapping --
|
||||
* because the field might not have been indexed yet, but we still need to display the mapping.
|
||||
*
|
||||
* @param mapping Mapping of a specified field name + value to a certain severity value.
|
||||
|
@ -307,8 +304,8 @@ export const SeverityField = ({
|
|||
*/
|
||||
const getFieldTypeByMapping = (
|
||||
mapping: SeverityMappingItem,
|
||||
pattern: IIndexPattern
|
||||
): IFieldType => {
|
||||
pattern: IndexPatternBase
|
||||
): IndexPatternFieldBase => {
|
||||
const { field } = mapping;
|
||||
const [knownFieldType] = pattern.fields.filter(({ name }) => field === name);
|
||||
return knownFieldType ?? { name: field, type: 'string' };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue