Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution][Detections] Rule forms cleanup #76138

Merged
merged 43 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c5c1675
Remove unused isNew field
rylnd Aug 21, 2020
b19ccad
WIP: Making rule form type safe
rylnd Aug 21, 2020
9155d9b
Make defaultValues a required prop of the define step
rylnd Aug 22, 2020
18a2104
Refactor rule creation forms to not require default values
rylnd Aug 24, 2020
7c3a807
Remove unnecessary "deep" comparison
rylnd Aug 24, 2020
2b66a8f
Make StepRuleDescription generic on its schema
rylnd Aug 25, 2020
e7556b7
Fixes bug introduced by form lib updates
rylnd Aug 25, 2020
fdf2cf4
Rename typed hook to obviate eslint exception
rylnd Aug 25, 2020
788135b
WIP: Fixing type errors in the other form steps
rylnd Aug 26, 2020
1cb55bf
More form cleanup
rylnd Aug 26, 2020
8bc8141
Fix some leftover type errors
rylnd Aug 27, 2020
a61744e
Remove duplicated useEffect hook
rylnd Aug 27, 2020
d80998a
Fix Rule edit form
rylnd Aug 27, 2020
ad8aa60
Fixes About Step jest tests
rylnd Aug 27, 2020
b174270
Fix bug with going to a previous step after editing actions
rylnd Aug 27, 2020
7fa8bca
Add assertions to our rule creation test
rylnd Aug 27, 2020
15c38bb
Simplify Rule Creation logic
rylnd Aug 31, 2020
4995e81
Don't persist empty form data when leaving a form step
rylnd Sep 1, 2020
453ef00
Skip About Step tests for now
rylnd Sep 1, 2020
aa70f70
Merge branch 'master' into rule_form_cleanup_and_prep
rylnd Sep 1, 2020
f36baf5
Remove unnecessary calls to setValue
rylnd Sep 1, 2020
d58545b
Style: logic cleanup
rylnd Sep 1, 2020
bab04a4
Prevent users from navigating away from an invalid step on rule edit
rylnd Sep 1, 2020
7a819cb
Display callout if attempting to navigate away from an invalid tab
rylnd Sep 1, 2020
39bd188
Persist our form submit() rather than the entire form
rylnd Sep 1, 2020
87b77bb
Replace FormDataProvider with useFormData hook
rylnd Sep 1, 2020
8cacf35
Move fetch of fields data _after_ form initialization
rylnd Sep 1, 2020
0972e16
Replace FormDataProvider on About step
rylnd Sep 1, 2020
84570c9
Replace local state with useFormData
rylnd Sep 1, 2020
3d64b01
Types cleanup
rylnd Sep 1, 2020
8f458a3
Rewrite About Step tests
rylnd Sep 2, 2020
deeaa48
Add memoization back to StepRuleDescription
rylnd Sep 2, 2020
aac72c9
Do not fetch ML Jobs if StepRuleDescription is not rendering ML Data
rylnd Sep 2, 2020
cf47e59
Fix bug where revisiting the About step could modify the user's Risk …
rylnd Sep 3, 2020
bf8e5c5
Clean up About Step tests
rylnd Sep 3, 2020
36ba03f
Fix local form data when form isn't mounted
rylnd Sep 3, 2020
33cdeef
Merge branch 'master' into rule_form_cleanup_and_prep
rylnd Sep 3, 2020
526929e
Allow user to navigate between invalid tabs on Edit Rule
rylnd Sep 3, 2020
db36f40
Fix logical error
rylnd Sep 3, 2020
9b16fe5
Merge branch 'master' into rule_form_cleanup_and_prep
rylnd Sep 4, 2020
7d359ed
Remove unneeded eslint exception
rylnd Sep 4, 2020
e45c128
Make 21 the default risk score for a new rule
rylnd Sep 4, 2020
b24742c
Remove duplicated type in favor of common one
rylnd Sep 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
createAndActivateRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
expectDefineFormToRepopulateAndContinue,
expectAboutFormToRepopulateAndContinue,
} from '../tasks/create_new_rule';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
Expand All @@ -82,6 +84,8 @@ describe('Detection rules, custom', () => {
goToCreateNewRule();
fillDefineCustomRuleWithImportedQueryAndContinue(newRule);
fillAboutRuleAndContinue(newRule);
expectDefineFormToRepopulateAndContinue(newRule);
expectAboutFormToRepopulateAndContinue(newRule);
createAndActivateRule();

cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

export const ABOUT_CONTINUE_BTN = '[data-test-subj="about-continue"]';

export const ABOUT_EDIT_BUTTON = '[data-test-subj="edit-about-rule"]';

export const ADD_FALSE_POSITIVE_BTN =
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text';

Expand All @@ -26,6 +28,8 @@ export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]';

export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]';

export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]';

export const IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK =
'[data-test-subj="importQueryFromSavedTimeline"]';

Expand Down
16 changes: 16 additions & 0 deletions x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import {
THRESHOLD_FIELD_SELECTION,
THRESHOLD_INPUT_AREA,
THRESHOLD_TYPE,
DEFINE_EDIT_BUTTON,
ABOUT_EDIT_BUTTON,
} from '../screens/create_new_rule';
import { TIMELINE } from '../screens/timeline';

Expand Down Expand Up @@ -175,6 +177,20 @@ export const fillDefineCustomRuleWithImportedQueryAndContinue = (
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
};

export const expectDefineFormToRepopulateAndContinue = (rule: CustomRule) => {
cy.get(DEFINE_EDIT_BUTTON).click();
cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist');
};

export const expectAboutFormToRepopulateAndContinue = (rule: CustomRule) => {
cy.get(ABOUT_EDIT_BUTTON).click();
cy.get(RULE_NAME_INPUT).invoke('val').should('eq', rule.name);
cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
cy.get(ABOUT_CONTINUE_BTN).should('not.exist');
};

export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
const thresholdField = 0;
const threshold = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ export interface WithKibanaProps {
kibana: KibanaContext;
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const typedUseKibana = () => useKibana<StartServices>();
const useTypedKibana = () => useKibana<StartServices>();

export {
KibanaContextProvider,
typedUseKibana as useKibana,
useTypedKibana as useKibana,
useUiSetting,
useUiSetting$,
withKibana,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React from 'react';
import { shallow, mount } from 'enzyme';

import {
StepRuleDescriptionComponent,
StepRuleDescription,
addFilterStateIfNotThere,
buildListItems,
getDescriptionItem,
Expand Down Expand Up @@ -52,24 +52,24 @@ describe('description_step', () => {
mockAboutStep = mockAboutStepRule();
});

describe('StepRuleDescriptionComponent', () => {
describe('StepRuleDescription', () => {
test('renders tow columns when "columns" is "multi"', () => {
const wrapper = shallow(
<StepRuleDescriptionComponent columns="multi" data={mockAboutStep} schema={schema} />
<StepRuleDescription columns="multi" data={mockAboutStep} schema={schema} />
);
expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(2);
});

test('renders single column when "columns" is "single"', () => {
const wrapper = shallow(
<StepRuleDescriptionComponent columns="single" data={mockAboutStep} schema={schema} />
<StepRuleDescription columns="single" data={mockAboutStep} schema={schema} />
);
expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(1);
});

test('renders one column with title and description split when "columns" is "singleSplit', () => {
const wrapper = shallow(
<StepRuleDescriptionComponent columns="singleSplit" data={mockAboutStep} schema={schema} />
<StepRuleDescription columns="singleSplit" data={mockAboutStep} schema={schema} />
);
expect(wrapper.find('[data-test-subj="listItemColumnStepRuleDescription"]')).toHaveLength(1);
expect(
Expand Down Expand Up @@ -299,7 +299,6 @@ describe('description_step', () => {
describe('queryBar', () => {
test('returns array of ListItems when queryBar exist', () => {
const mockQueryBar = {
isNew: false,
queryBar: {
query: {
query: 'user.name: root or user.name: admin',
Expand Down Expand Up @@ -369,7 +368,6 @@ describe('description_step', () => {
describe('threshold', () => {
test('returns threshold description when threshold exist and field is empty', () => {
const mockThreshold = {
isNew: false,
threshold: {
field: [''],
value: 100,
Expand All @@ -391,7 +389,6 @@ describe('description_step', () => {

test('returns threshold description when threshold exist and field is set', () => {
const mockThreshold = {
isNew: false,
threshold: {
field: ['user.name'],
value: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { isEmpty, chunk, get, pick, isNumber } from 'lodash/fp';
import React, { memo, useState } from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';

import { RuleType } from '../../../../../common/detection_engine/types';
Expand Down Expand Up @@ -52,19 +52,19 @@ const DescriptionListContainer = styled(EuiDescriptionList)`
}
`;

interface StepRuleDescriptionProps {
interface StepRuleDescriptionProps<T> {
columns?: 'multi' | 'single' | 'singleSplit';
data: unknown;
indexPatterns?: IIndexPattern;
schema: FormSchema;
schema: FormSchema<T>;
}

export const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> = ({
export const StepRuleDescription = <T,>({
data,
columns = 'multi',
indexPatterns,
schema,
}) => {
}: StepRuleDescriptionProps<T>) => {
const kibana = useKibana();
const [filterManager] = useState<FilterManager>(new FilterManager(kibana.services.uiSettings));
const { jobs } = useSecurityJobs(false);
Expand Down Expand Up @@ -125,11 +125,9 @@ export const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> =
);
};

export const StepRuleDescription = memo(StepRuleDescriptionComponent);

export const buildListItems = (
export const buildListItems = <T,>(
data: unknown,
schema: FormSchema,
schema: FormSchema<T>,
filterManager: FilterManager,
indexPatterns?: IIndexPattern
): ListItems[] =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elasti
import * as RuleI18n from '../../../pages/detection_engine/rules/translations';

interface NextStepProps {
onClick: () => Promise<void>;
onClick: () => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

isDisabled: boolean;
dataTestSubj?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const stepAboutDefaultValue: AboutStepRule = {
description: '',
isAssociatedToEndpointList: false,
isBuildingBlock: false,
isNew: true,
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
riskScore: { value: 50, mapping: [], isMappingChecked: false },
references: [''],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import { StepAboutRule } from '.';
import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock';
import { StepRuleDescription } from '../description_step';
import { stepAboutDefaultValue } from './default_value';
// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
import { wait as waitFor } from '@testing-library/react';
import { AboutStepRule } from '../../../pages/detection_engine/rules/types';
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';

Expand All @@ -32,7 +30,7 @@ afterAll(() => {
});
/* eslint-enable no-console */

describe('StepAboutRuleComponent', () => {
describe.skip('StepAboutRuleComponent', () => {
test('it renders StepRuleDescription if isReadOnlyView is true and "name" property exists', () => {
const wrapper = shallow(
<StepAboutRule
Expand All @@ -57,7 +55,6 @@ describe('StepAboutRuleComponent', () => {
isReadOnlyView={false}
isLoading={false}
setForm={jest.fn()}
setStepData={jest.fn()}
/>
</ThemeProvider>
);
Expand Down Expand Up @@ -101,7 +98,6 @@ describe('StepAboutRuleComponent', () => {
isReadOnlyView={false}
isLoading={false}
setForm={jest.fn()}
setStepData={jest.fn()}
/>
</ThemeProvider>
);
Expand Down Expand Up @@ -149,7 +145,6 @@ describe('StepAboutRuleComponent', () => {
isReadOnlyView={false}
isLoading={false}
setForm={jest.fn()}
setStepData={stepDataMock}
/>
</ThemeProvider>
);
Expand All @@ -164,33 +159,34 @@ describe('StepAboutRuleComponent', () => {
.first()
.simulate('change', { target: { value: 'Test name text' } });

wrapper.find('button[data-test-subj="about-continue"]').first().simulate('click').update();
await waitFor(() => {
const expected: Omit<AboutStepRule, 'isNew'> = {
author: [],
isAssociatedToEndpointList: false,
isBuildingBlock: false,
license: '',
ruleNameOverride: '',
timestampOverride: '',
description: 'Test description text',
falsePositives: [''],
name: 'Test name text',
note: '',
references: [''],
riskScore: { value: 50, mapping: [], isMappingChecked: false },
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
tags: [],
threat: [
{
framework: 'MITRE ATT&CK',
tactic: { id: 'none', name: 'none', reference: 'none' },
technique: [],
},
],
};
expect(stepDataMock.mock.calls[1][1]).toEqual(expected);
await act(async () => {
wrapper.find('button[data-test-subj="about-continue"]').first().simulate('click').update();
});
const expected: AboutStepRule = {
author: [],
isAssociatedToEndpointList: false,
isBuildingBlock: false,
license: '',
ruleNameOverride: '',
timestampOverride: '',
description: 'Test description text',
falsePositives: [''],
name: 'Test name text',
note: '',
references: [''],
riskScore: { value: 50, mapping: [], isMappingChecked: false },
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
tags: [],
threat: [
{
framework: 'MITRE ATT&CK',
tactic: { id: 'none', name: 'none', reference: 'none' },
technique: [],
},
],
};

expect(stepDataMock.mock.calls[0][1]).toEqual(expected);
});

test('it allows user to set the risk score as a number (and not a string)', async () => {
Expand All @@ -204,7 +200,6 @@ describe('StepAboutRuleComponent', () => {
isReadOnlyView={false}
isLoading={false}
setForm={jest.fn()}
setStepData={stepDataMock}
/>
</ThemeProvider>
);
Expand Down Expand Up @@ -251,6 +246,6 @@ describe('StepAboutRuleComponent', () => {
},
],
};
expect(stepDataMock.mock.calls[1][1]).toEqual(expected);
expect(stepDataMock.mock.calls[0][1]).toEqual(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui';
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
import React, { FC, memo, useCallback, useEffect } from 'react';
import styled from 'styled-components';

import { isMlRule } from '../../../../../common/machine_learning/helpers';
Expand Down Expand Up @@ -68,11 +68,10 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
isReadOnlyView,
isUpdateView = false,
isLoading,
onSubmit,
setForm,
setStepData,
}) => {
const initialState = defaultValues ?? stepAboutDefaultValue;
const [myStepData, setMyStepData] = useState<AboutStepRule>(initialState);
const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
defineRuleData?.index ?? [],
'step_about_rule'
Expand All @@ -82,33 +81,28 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
!isMlRule(defineRuleData.ruleType) &&
!isThresholdRule(defineRuleData.ruleType);

const { form } = useForm({
const { form } = useForm<AboutStepRule>({
defaultValue: initialState,
options: { stripEmptyFields: false },
schema,
});
const { getFields, submit } = form;
const { getFields } = form;

const onSubmit = useCallback(async () => {
if (setStepData) {
setStepData(RuleStep.aboutRule, null, false);
const { isValid, data } = await submit();
if (isValid) {
setStepData(RuleStep.aboutRule, data, isValid);
setMyStepData({ ...data, isNew: false } as AboutStepRule);
}
const handleSubmit = useCallback(() => {
if (onSubmit) {
onSubmit();
}
}, [setStepData, submit]);
}, [onSubmit]);

useEffect(() => {
if (setForm) {
setForm(RuleStep.aboutRule, form);
}
}, [setForm, form]);

return isReadOnlyView && myStepData.name != null ? (
return isReadOnlyView ? (
<StepContentWrapper data-test-subj="aboutStep" addPadding={addPadding}>
<StepRuleDescription columns={descriptionColumns} schema={schema} data={myStepData} />
<StepRuleDescription columns={descriptionColumns} schema={schema} data={initialState} />
</StepContentWrapper>
) : (
<>
Expand Down Expand Up @@ -324,7 +318,7 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
</StepContentWrapper>

{!isUpdateView && (
<NextStep dataTestSubj="about-continue" onClick={onSubmit} isDisabled={isLoading} />
<NextStep dataTestSubj="about-continue" onClick={handleSubmit} isDisabled={isLoading} />
)}
</>
);
Expand Down
Loading