Skip to content

Commit

Permalink
[8.x] [Security Solution] `FinalEdit`: Add fields that are …
Browse files Browse the repository at this point in the history
…common for all rule types (#196642) (#199743)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] `FinalEdit`: Add fields that are common
for all rule types
(#196642)](#196642)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Nikita
Indik","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-12T10:04:10Z","message":"[Security
Solution] `FinalEdit`: Add fields that are common for all rule types
(#196642)\n\n**Partially addresses:
https://github.com/elastic/kibana/issues/171520**\r\n**Is a follow-up
to: https://github.com/elastic/kibana/pull/196326**\r\n\r\nThis PR
enables editing of common fields in the new \"Updates\" tab of the rule
upgrade flyout. The common fields are fields applicable to all rule
types.\r\n\r\n## Summary\r\nThese fields are editable now:\r\n -
`building_block`\r\n - `description`\r\n - `false_positives`\r\n -
`investigation_fields`\r\n - `max_signals`\r\n - `note`\r\n -
`references`\r\n - `related_integrations`\r\n - `required_fields`\r\n -
`risk_score`\r\n - `risk_score_mapping`\r\n - `rule_name_override`\r\n -
`rule_schedule`\r\n - `setup`\r\n - `severity`\r\n -
`severity_mapping`\r\n - `tags`\r\n - `threat`\r\n -
`timeline_template`\r\n - `timestamp_override`\r\n\r\n<img
width=\"2672\" alt=\"Scherm­afbeelding 2024-10-16 om 17 32 06\"
src=\"https://github.com/user-attachments/assets/6dd615e2-6e84-4e1f-b674-f42d03f575e7\">\r\n\r\n###
Testing\r\n - Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled.\r\n - To simulate the availability of prebuilt rule
upgrades, downgrade a currently installed prebuilt rule using the `PATCH
api/detection_engine/rules` API. \r\n - Set `version: 1` in the request
body to downgrade it to version 1.\r\n - Modify other rule fields in the
request body as needed to test the
changes.","sha":"3d3b32faf6992f95805a37230e7e7e552e19a801","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection
Rules","backport:prev-minor"],"title":"[Security Solution] `FinalEdit`:
Add fields that are common for all rule
types","number":196642,"url":"https://github.com/elastic/kibana/pull/196642","mergeCommit":{"message":"[Security
Solution] `FinalEdit`: Add fields that are common for all rule types
(#196642)\n\n**Partially addresses:
https://github.com/elastic/kibana/issues/171520**\r\n**Is a follow-up
to: https://github.com/elastic/kibana/pull/196326**\r\n\r\nThis PR
enables editing of common fields in the new \"Updates\" tab of the rule
upgrade flyout. The common fields are fields applicable to all rule
types.\r\n\r\n## Summary\r\nThese fields are editable now:\r\n -
`building_block`\r\n - `description`\r\n - `false_positives`\r\n -
`investigation_fields`\r\n - `max_signals`\r\n - `note`\r\n -
`references`\r\n - `related_integrations`\r\n - `required_fields`\r\n -
`risk_score`\r\n - `risk_score_mapping`\r\n - `rule_name_override`\r\n -
`rule_schedule`\r\n - `setup`\r\n - `severity`\r\n -
`severity_mapping`\r\n - `tags`\r\n - `threat`\r\n -
`timeline_template`\r\n - `timestamp_override`\r\n\r\n<img
width=\"2672\" alt=\"Scherm­afbeelding 2024-10-16 om 17 32 06\"
src=\"https://github.com/user-attachments/assets/6dd615e2-6e84-4e1f-b674-f42d03f575e7\">\r\n\r\n###
Testing\r\n - Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled.\r\n - To simulate the availability of prebuilt rule
upgrades, downgrade a currently installed prebuilt rule using the `PATCH
api/detection_engine/rules` API. \r\n - Set `version: 1` in the request
body to downgrade it to version 1.\r\n - Modify other rule fields in the
request body as needed to test the
changes.","sha":"3d3b32faf6992f95805a37230e7e7e552e19a801"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196642","number":196642,"mergeCommit":{"message":"[Security
Solution] `FinalEdit`: Add fields that are common for all rule types
(#196642)\n\n**Partially addresses:
https://github.com/elastic/kibana/issues/171520**\r\n**Is a follow-up
to: https://github.com/elastic/kibana/pull/196326**\r\n\r\nThis PR
enables editing of common fields in the new \"Updates\" tab of the rule
upgrade flyout. The common fields are fields applicable to all rule
types.\r\n\r\n## Summary\r\nThese fields are editable now:\r\n -
`building_block`\r\n - `description`\r\n - `false_positives`\r\n -
`investigation_fields`\r\n - `max_signals`\r\n - `note`\r\n -
`references`\r\n - `related_integrations`\r\n - `required_fields`\r\n -
`risk_score`\r\n - `risk_score_mapping`\r\n - `rule_name_override`\r\n -
`rule_schedule`\r\n - `setup`\r\n - `severity`\r\n -
`severity_mapping`\r\n - `tags`\r\n - `threat`\r\n -
`timeline_template`\r\n - `timestamp_override`\r\n\r\n<img
width=\"2672\" alt=\"Scherm­afbeelding 2024-10-16 om 17 32 06\"
src=\"https://github.com/user-attachments/assets/6dd615e2-6e84-4e1f-b674-f42d03f575e7\">\r\n\r\n###
Testing\r\n - Ensure the `prebuiltRulesCustomizationEnabled` feature
flag is enabled.\r\n - To simulate the availability of prebuilt rule
upgrades, downgrade a currently installed prebuilt rule using the `PATCH
api/detection_engine/rules` API. \r\n - Set `version: 1` in the request
body to downgrade it to version 1.\r\n - Modify other rule fields in the
request body as needed to test the
changes.","sha":"3d3b32faf6992f95805a37230e7e7e552e19a801"}}]}]
BACKPORT-->

Co-authored-by: Nikita Indik <[email protected]>
  • Loading branch information
kibanamachine and nikitaindik authored Nov 12, 2024
1 parent 04a6dd9 commit bb3e3c7
Show file tree
Hide file tree
Showing 69 changed files with 2,248 additions and 534 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-securitysolution-autocomplete/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

export * from './src/check_empty_value';
export * from './src/field';
export * from './src/es_field_selector';
export * from './src/field_value_exists';
export * from './src/field_value_lists';
export * from './src/field_value_match';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import React from 'react';
import { fireEvent, render, waitFor, within } from '@testing-library/react';
import '@testing-library/jest-dom';

import { FieldComponent } from '..';
import { EsFieldSelector } from '..';
import { fields, getField } from '../../fields/index.mock';

describe('FieldComponent', () => {
it('should render the component enabled and displays the selected field correctly', () => {
const wrapper = render(
<FieldComponent
<EsFieldSelector
isClearable={false}
isDisabled={false}
isLoading={false}
Expand All @@ -38,7 +38,7 @@ describe('FieldComponent', () => {
});
it('should render the component disabled if isDisabled is true', () => {
const wrapper = render(
<FieldComponent
<EsFieldSelector
isClearable={false}
isDisabled={true}
isLoading={false}
Expand All @@ -57,7 +57,7 @@ describe('FieldComponent', () => {
});
it('should render the loading spinner if isLoading is true when clicked', () => {
const wrapper = render(
<FieldComponent
<EsFieldSelector
isClearable={false}
isDisabled={true}
isLoading={true}
Expand All @@ -78,7 +78,7 @@ describe('FieldComponent', () => {
});
it('should allow user to clear values if isClearable is true', () => {
const wrapper = render(
<FieldComponent
<EsFieldSelector
indexPattern={{
fields,
id: '1234',
Expand All @@ -97,7 +97,7 @@ describe('FieldComponent', () => {
});
it('should change the selected value', async () => {
const wrapper = render(
<FieldComponent
<EsFieldSelector
isClearable={false}
isDisabled={true}
isLoading={false}
Expand All @@ -119,7 +119,7 @@ describe('FieldComponent', () => {
it('it allows custom user input if "acceptsCustomOptions" is "true"', async () => {
const mockOnChange = jest.fn();
const wrapper = render(
<FieldComponent
<EsFieldSelector
indexPattern={{
fields,
id: '1234',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import TestRenderer from 'react-test-renderer';
const { act: actTestRenderer } = TestRenderer;

import { fields } from '../../fields/index.mock';
import { useField } from '../use_field';
import { useEsField } from '../use_es_field';

jest.mock('../../translations', () => ({
BINARY_TYPE_NOT_SUPPORTED: 'Binary fields are currently unsupported',
Expand All @@ -33,7 +33,7 @@ describe('useField', () => {

describe('comboOptions and selectedComboOptions', () => {
it('should return default values', () => {
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
const { isInvalid, comboOptions, selectedComboOptions, fieldWidth } = result.current;
expect(isInvalid).toBeFalsy();
expect(comboOptions.length).toEqual(30);
Expand Down Expand Up @@ -79,7 +79,7 @@ describe('useField', () => {
};

const { result } = renderHook(() =>
useField({ indexPattern: newIndexPattern, onChange: onChangeMock })
useEsField({ indexPattern: newIndexPattern, onChange: onChangeMock })
);
const { comboOptions, selectedComboOptions } = result.current;
expect(comboOptions).toEqual([{ label: 'bytes' }, { label: 'ssl' }, { label: '@timestamp' }]);
Expand Down Expand Up @@ -124,7 +124,7 @@ describe('useField', () => {
};

const { result } = renderHook(() =>
useField({
useEsField({
indexPattern: newIndexPattern,
onChange: onChangeMock,
selectedField: { name: '', type: 'keyword' },
Expand Down Expand Up @@ -173,7 +173,7 @@ describe('useField', () => {
};

const { result } = renderHook(() =>
useField({
useEsField({
indexPattern: newIndexPattern,
onChange: onChangeMock,
selectedField: { name: ' ', type: 'keyword' },
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('useField', () => {
};

const { result } = renderHook(() =>
useField({ indexPattern: newIndexPattern, onChange: onChangeMock, selectedField })
useEsField({ indexPattern: newIndexPattern, onChange: onChangeMock, selectedField })
);
const { comboOptions, selectedComboOptions } = result.current;
expect(comboOptions).toEqual([{ label: 'bytes' }, { label: 'ssl' }, { label: '@timestamp' }]);
Expand Down Expand Up @@ -273,7 +273,7 @@ describe('useField', () => {
readFromDocValues: true,
},
] as unknown as DataViewFieldBase[];
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
const { comboOptions, renderFields } = result.current;
expect(comboOptions).toEqual([
{ label: 'blob' },
Expand Down Expand Up @@ -328,7 +328,7 @@ describe('useField', () => {
readFromDocValues: true,
},
] as unknown as DataViewFieldBase[];
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
const { comboOptions, renderFields } = result.current;
expect(comboOptions).toEqual([
{ label: 'blob' },
Expand Down Expand Up @@ -374,7 +374,7 @@ describe('useField', () => {
readFromDocValues: true,
},
] as unknown as DataViewFieldBase[];
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
const { comboOptions, renderFields } = result.current;
expect(comboOptions).toEqual([{ label: 'bytes' }, { label: 'ssl' }, { label: '@timestamp' }]);
act(() => {
Expand All @@ -389,7 +389,7 @@ describe('useField', () => {
jest.resetModules();
});
it('should invoke onChange with one value if one option is sent', () => {
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
act(() => {
result.current.handleValuesChange([
{
Expand All @@ -411,7 +411,7 @@ describe('useField', () => {
});
});
it('should invoke onChange with array value if more than an option', () => {
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
act(() => {
result.current.handleValuesChange([
{
Expand Down Expand Up @@ -446,7 +446,7 @@ describe('useField', () => {
});
});
it('should invoke onChange with custom option if one is sent', () => {
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));
act(() => {
result.current.handleCreateCustomOption('madeUpField');
expect(onChangeMock).toHaveBeenCalledWith([
Expand All @@ -462,13 +462,13 @@ describe('useField', () => {
describe('fieldWidth', () => {
it('should return object has width prop', () => {
const { result } = renderHook(() =>
useField({ indexPattern, onChange: onChangeMock, fieldInputWidth: 100 })
useEsField({ indexPattern, onChange: onChangeMock, fieldInputWidth: 100 })
);
expect(result.current.fieldWidth).toEqual({ width: '100px' });
});
it('should return empty object', () => {
const { result } = renderHook(() =>
useField({ indexPattern, onChange: onChangeMock, fieldInputWidth: 0 })
useEsField({ indexPattern, onChange: onChangeMock, fieldInputWidth: 0 })
);
expect(result.current.fieldWidth).toEqual({});
});
Expand All @@ -477,7 +477,7 @@ describe('useField', () => {
describe('isInvalid with handleTouch', () => {
it('should return isInvalid equals true when calling with no selectedField and isRequired is true', () => {
const { result } = renderHook(() =>
useField({ indexPattern, onChange: onChangeMock, isRequired: true })
useEsField({ indexPattern, onChange: onChangeMock, isRequired: true })
);

actTestRenderer(() => {
Expand All @@ -487,7 +487,7 @@ describe('useField', () => {
});
it('should return isInvalid equals false with selectedField and isRequired is true', () => {
const { result } = renderHook(() =>
useField({ indexPattern, onChange: onChangeMock, isRequired: true, selectedField })
useEsField({ indexPattern, onChange: onChangeMock, isRequired: true, selectedField })
);

actTestRenderer(() => {
Expand All @@ -496,7 +496,7 @@ describe('useField', () => {
expect(result.current.isInvalid).toBeFalsy();
});
it('should return isInvalid equals false when isRequired is false', () => {
const { result } = renderHook(() => useField({ indexPattern, onChange: onChangeMock }));
const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock }));

actTestRenderer(() => {
result.current.handleTouch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ import React from 'react';
import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FieldProps } from './types';
import { useField } from './use_field';
import { FieldBaseProps } from './types';
import { useEsField } from './use_es_field';

const AS_PLAIN_TEXT = { asPlainText: true };

export const FieldComponent: React.FC<FieldProps> = ({
interface EsFieldSelectorProps extends FieldBaseProps {
isClearable?: boolean;
isDisabled?: boolean;
isLoading?: boolean;
placeholder: string;
acceptsCustomOptions?: boolean;
showMappingConflicts?: boolean;
'aria-label'?: string;
}

export function EsFieldSelector({
fieldInputWidth,
fieldTypeFilter = [],
indexPattern,
Expand All @@ -30,18 +40,17 @@ export const FieldComponent: React.FC<FieldProps> = ({
acceptsCustomOptions = false,
showMappingConflicts = false,
'aria-label': ariaLabel,
}): JSX.Element => {
}: EsFieldSelectorProps): JSX.Element {
const {
isInvalid,
comboOptions,
selectedComboOptions,
fieldWidth,

renderFields,
handleTouch,
handleValuesChange,
handleCreateCustomOption,
} = useField({
} = useEsField({
indexPattern,
fieldTypeFilter,
isRequired,
Expand Down Expand Up @@ -97,6 +106,4 @@ export const FieldComponent: React.FC<FieldProps> = ({
aria-label={ariaLabel}
/>
);
};

FieldComponent.displayName = 'Field';
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ import { DataViewBase, DataViewFieldBase } from '@kbn/es-query';
import { FieldConflictsInfo } from '@kbn/securitysolution-list-utils';
import { GetGenericComboBoxPropsReturn } from '../get_generic_combo_box_props';

export interface FieldProps extends FieldBaseProps {
isClearable: boolean;
isDisabled: boolean;
isLoading: boolean;
placeholder: string;
acceptsCustomOptions?: boolean;
showMappingConflicts?: boolean;
'aria-label'?: string;
}
export interface FieldBaseProps {
indexPattern: DataViewBase | undefined;
fieldTypeFilter?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const getComboBoxProps = (fields: ComboBoxFields): GetFieldComboBoxPropsReturn =
};
};

export const useField = ({
export const useEsField = ({
indexPattern,
fieldTypeFilter,
isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ interface AutocompleteFieldMatchProps {
selectedField: DataViewFieldBase | undefined;
selectedValue: string | undefined;
indexPattern: DataViewBase | undefined;
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
isLoading?: boolean;
isDisabled?: boolean;
isClearable?: boolean;
isRequired?: boolean;
fieldInputWidth?: number;
rowLabel?: string;
Expand All @@ -68,7 +68,7 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
selectedField,
selectedValue,
indexPattern,
isLoading,
isLoading = false,
isDisabled = false,
isClearable = false,
isRequired = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
AutocompleteFieldMatchAnyComponent,
AutocompleteFieldMatchComponent,
AutocompleteFieldWildcardComponent,
FieldComponent,
EsFieldSelector,
OperatorComponent,
} from '@kbn/securitysolution-autocomplete';
import {
Expand Down Expand Up @@ -207,7 +207,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
(isFirst: boolean): JSX.Element => {
const filteredIndexPatterns = getFilteredIndexPatterns(indexPattern, entry);
const comboBox = (
<FieldComponent
<EsFieldSelector
placeholder={
entry.nested != null
? i18n.EXCEPTION_FIELD_NESTED_PLACEHOLDER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React, { useCallback, useMemo } from 'react';
import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';

import { FieldComponent } from '@kbn/securitysolution-autocomplete';
import { EsFieldSelector } from '@kbn/securitysolution-autocomplete';
import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query';
import type { FormattedEntry, Entry } from './types';
import * as i18n from './translations';
Expand Down Expand Up @@ -57,7 +57,7 @@ export const EntryItem: React.FC<EntryItemProps> = ({

const renderFieldInput = useMemo(() => {
const comboBox = (
<FieldComponent
<EsFieldSelector
placeholder={i18n.FIELD_PLACEHOLDER}
indexPattern={indexPattern}
selectedField={entry.field}
Expand Down Expand Up @@ -87,7 +87,7 @@ export const EntryItem: React.FC<EntryItemProps> = ({

const renderThreatFieldInput = useMemo(() => {
const comboBox = (
<FieldComponent
<EsFieldSelector
placeholder={i18n.FIELD_PLACEHOLDER}
indexPattern={threatIndexPatterns}
selectedField={entry.value}
Expand Down
Loading

0 comments on commit bb3e3c7

Please sign in to comment.