Skip to content

Commit

Permalink
use form field for EQL Options
Browse files Browse the repository at this point in the history
  • Loading branch information
maximpn committed Nov 18, 2024
1 parent 76fed4e commit b202b54
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ export const validateEql = async ({
params: {
index: dataViewTitle,
body: { query, runtime_mappings: runtimeMappings, size: 0 },
timestamp_field: eqlOptions?.timestampField,
tiebreaker_field: eqlOptions?.tiebreakerField,
event_category_field: eqlOptions?.eventCategoryField,
// Prevent passing empty string values
timestamp_field: eqlOptions?.timestampField ? eqlOptions.timestampField : undefined,
tiebreaker_field: eqlOptions?.tiebreakerField ? eqlOptions.tiebreakerField : undefined,
event_category_field: eqlOptions?.eventCategoryField
? eqlOptions.eventCategoryField
: undefined,
},
options: { ignore: [400] },
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { render, screen, fireEvent, within } from '@testing-library/react';

import { mockIndexPattern, TestProviders, useFormFieldMock } from '../../../../common/mock';
import { mockQueryBar } from '../../../rule_management_ui/components/rules_table/__mocks__/mock';
import { selectEuiComboBoxOption } from '../../../../common/test/eui/combobox';
import type { EqlQueryBarProps } from './eql_query_bar';
import { EqlQueryBar } from './eql_query_bar';
import { getEqlValidationError } from './validators.mock';
Expand Down Expand Up @@ -116,35 +117,63 @@ describe('EqlQueryBar', () => {

describe('EQL options interaction', () => {
const mockOptionsData = {
keywordFields: [],
keywordFields: [{ label: 'category', value: 'category' }],
dateFields: [{ label: 'timestamp', value: 'timestamp' }],
nonDateFields: [],
nonDateFields: [{ label: 'tiebreaker', value: 'tiebreaker' }],
};

it('invokes onOptionsChange when the EQL options change', () => {
const onOptionsChangeMock = jest.fn();
it('updates EQL options', async () => {
let eqlOptions = {};

const { getByTestId, getByText } = render(
const mockEqlOptionsField = useFormFieldMock({
value: {},
setValue: (updater) => {
if (typeof updater === 'function') {
eqlOptions = updater(eqlOptions);
}
},
});

const { getByTestId } = render(
<TestProviders>
<EqlQueryBar
dataTestSubj="myQueryBar"
field={mockField}
eqlOptionsField={mockEqlOptionsField}
isLoading={false}
eqlFieldsComboBoxOptions={mockOptionsData}
indexPattern={mockIndexPattern}
onEqlOptionsChange={onOptionsChangeMock}
/>
</TestProviders>
);

// open options popover
fireEvent.click(getByTestId('eql-settings-trigger'));
// display combobox options
within(getByTestId(`eql-timestamp-field`)).getByRole('combobox').focus();
// select timestamp
getByText('timestamp').click();

expect(onOptionsChangeMock).toHaveBeenCalledWith('timestampField', 'timestamp');
await selectEuiComboBoxOption({
comboBoxToggleButton: within(getByTestId('eql-event-category-field')).getByRole('combobox'),
optionText: 'category',
});

expect(eqlOptions).toEqual({ eventCategoryField: 'category' });

await selectEuiComboBoxOption({
comboBoxToggleButton: within(getByTestId('eql-tiebreaker-field')).getByRole('combobox'),
optionText: 'tiebreaker',
});

expect(eqlOptions).toEqual({ eventCategoryField: 'category', tiebreakerField: 'tiebreaker' });

await selectEuiComboBoxOption({
comboBoxToggleButton: within(getByTestId('eql-timestamp-field')).getByRole('combobox'),
optionText: 'timestamp',
});

expect(eqlOptions).toEqual({
eventCategoryField: 'category',
tiebreakerField: 'tiebreaker',
timestampField: 'timestamp',
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ import { FilterManager } from '@kbn/data-plugin/public';
import type { FieldHook } from '../../../../shared_imports';
import { FilterBar } from '../../../../common/components/filter_bar';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types';
import * as i18n from './translations';
import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy';
import { useKibana } from '../../../../common/lib/kibana';
import type { FieldValueQueryBar } from '../query_bar';
import type { EqlQueryBarFooterProps } from './footer';
import { EqlQueryBarFooter } from './footer';
import { getValidationResults } from './validators';
import type {
EqlFieldsComboBoxOptions,
EqlOptions,
FieldsEqlOptions,
} from '../../../../../common/search_strategy';
import { useKibana } from '../../../../common/lib/kibana';
import * as i18n from './translations';

const TextArea = styled(EuiTextArea)`
display: block;
Expand Down Expand Up @@ -60,30 +57,28 @@ const StyledFormRow = styled(EuiFormRow)`

export interface EqlQueryBarProps {
dataTestSubj: string;
field: FieldHook<DefineStepRule['queryBar']>;
isLoading: boolean;
field: FieldHook<FieldValueQueryBar>;
eqlOptionsField?: FieldHook<EqlOptions>;
isLoading?: boolean;
indexPattern: DataViewBase;
showFilterBar?: boolean;
idAria?: string;
eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions;
eqlOptions?: EqlOptions;
isSizeOptionDisabled?: boolean;
onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void;
onValidityChange?: (arg: boolean) => void;
onValidatingChange?: (arg: boolean) => void;
}

export const EqlQueryBar: FC<EqlQueryBarProps> = ({
dataTestSubj,
field,
isLoading,
eqlOptionsField,
isLoading = false,
indexPattern,
showFilterBar,
idAria,
eqlFieldsComboBoxOptions,
eqlOptions: optionsSelected,
isSizeOptionDisabled,
onEqlOptionsChange,
onValidityChange,
onValidatingChange,
}) => {
Expand Down Expand Up @@ -172,6 +167,18 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({
[fieldValue, setFieldValue, onValidatingChange]
);

const handleEqlOptionsChange = useCallback<
NonNullable<EqlQueryBarFooterProps['onEqlOptionsChange']>
>(
(eqlOptionsFieldName, value) => {
eqlOptionsField?.setValue((prevEqlOptions) => ({
...prevEqlOptions,
[eqlOptionsFieldName]: value,
}));
},
[eqlOptionsField]
);

return (
<StyledFormRow
label={field.label}
Expand All @@ -196,8 +203,8 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({
isLoading={isValidating}
isSizeOptionDisabled={isSizeOptionDisabled}
optionsData={eqlFieldsComboBoxOptions}
optionsSelected={optionsSelected}
onOptionsChange={onEqlOptionsChange}
eqlOptions={eqlOptionsField?.value}
onEqlOptionsChange={handleEqlOptionsChange}
/>
{showFilterBar && (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@
import React, { useMemo } from 'react';
import type { DataViewBase } from '@kbn/es-query';
import type { FieldConfig } from '../../../../shared_imports';
import { UseField } from '../../../../shared_imports';
import type {
EqlFieldsComboBoxOptions,
EqlOptions,
FieldsEqlOptions,
} from '../../../../../common/search_strategy';
import { UseField, UseMultiFields } from '../../../../shared_imports';
import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy';
import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory';
import { debounceAsync } from '../../validators/debounce_async';
import { eqlQueryValidatorFactory } from '../../validators/eql_query_validator_factory';
Expand All @@ -23,40 +19,34 @@ import type { FieldValueQueryBar } from '../query_bar';

interface EqlQueryEditProps {
path: string;
eqlOptionsPath?: string;
fieldsToValidateOnChange?: string | string[];
eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions;
eqlOptions?: EqlOptions;
showEqlSizeOption?: boolean;
showFilterBar?: boolean;
dataView: DataViewBase;
required?: boolean;
loading?: boolean;
disabled?: boolean;
onEqlOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void;
onValidityChange?: (arg: boolean) => void;
onValidatingChange?: (arg: boolean) => void;
}

export function EqlQueryEdit({
path,
eqlOptionsPath,
fieldsToValidateOnChange,
eqlFieldsComboBoxOptions,
eqlOptions,
showEqlSizeOption = false,
showFilterBar = false,
dataView,
required,
loading,
disabled,
onEqlOptionsChange,
onValidityChange,
onValidatingChange,
}: EqlQueryEditProps): JSX.Element {
const componentProps = useMemo(
() => ({
eqlFieldsComboBoxOptions,
eqlOptions,
onEqlOptionsChange,
isSizeOptionDisabled: !showEqlSizeOption,
isDisabled: disabled,
isLoading: loading,
Expand All @@ -65,16 +55,12 @@ export function EqlQueryEdit({
idAria: 'ruleEqlQueryBar',
dataTestSubj: 'ruleEqlQueryBar',
onValidityChange,
onValidatingChange,
}),
[
eqlFieldsComboBoxOptions,
eqlOptions,
showEqlSizeOption,
showFilterBar,
onEqlOptionsChange,
onValidityChange,
onValidatingChange,
dataView,
loading,
disabled,
Expand All @@ -95,26 +81,52 @@ export function EqlQueryEdit({
]
: []),
{
validator: debounceAsync(
eqlQueryValidatorFactory(
validator: debounceAsync((...args) => {
const [{ formData }] = args;
const eqlOptions =
eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {};

return eqlQueryValidatorFactory(
dataView.id
? {
dataViewId: dataView.id,
eqlOptions: eqlOptions ?? {},
eqlOptions,
}
: {
indexPatterns: dataView.title.split(','),
eqlOptions: eqlOptions ?? {},
eqlOptions,
}
),
300
),
)(...args);
}, 300),
},
],
}),
[required, dataView.id, dataView.title, eqlOptions, path, fieldsToValidateOnChange]
[eqlOptionsPath, required, dataView.id, dataView.title, path, fieldsToValidateOnChange]
);

if (eqlOptionsPath) {
return (
<UseMultiFields<{
eqlQuery: FieldValueQueryBar;
eqlOptions: EqlOptions;
}>
fields={{
eqlQuery: {
path,
config: fieldConfig,
},
eqlOptions: {
path: eqlOptionsPath,
},
}}
>
{({ eqlQuery, eqlOptions }) => (
<EqlQueryBar field={eqlQuery} eqlOptionsField={eqlOptions} {...componentProps} />
)}
</UseMultiFields>
);
}

return (
<UseField
path={path}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('EQL footer', () => {
it('EQL settings button is enable when popover is NOT open', () => {
const wrapper = mount(
<TestProviders>
<EqlQueryBarFooter errors={[]} onOptionsChange={jest.fn()} />
<EqlQueryBarFooter errors={[]} onEqlOptionsChange={jest.fn()} />
</TestProviders>
);

Expand All @@ -44,7 +44,7 @@ describe('EQL footer', () => {
it('disable EQL settings button when popover is open', () => {
const wrapper = mount(
<TestProviders>
<EqlQueryBarFooter errors={[]} onOptionsChange={jest.fn()} />
<EqlQueryBarFooter errors={[]} onEqlOptionsChange={jest.fn()} />
</TestProviders>
);
wrapper.find(`[data-test-subj="eql-settings-trigger"]`).first().simulate('click');
Expand Down
Loading

0 comments on commit b202b54

Please sign in to comment.