[Security Solutions][Detection Engine] Updates the edit rules page to only have what is selected for editing (#79233)
## Summary Before when you would edit rules you get all the rules as disabled but you cannot switch between them in edit mode as it's already a rule you created: <img width="1063" alt="Screen Shot 2020-10-01 at 5 06 18 PM" src="https://user-images.githubusercontent.com/1151048/94872518-0bdaba00-040a-11eb-8b7d-3b3a59980e99.png"> After, now we remove those cards and only show the card of the rule type you're editing: <img width="1074" alt="Screen Shot 2020-10-02 at 6 29 48 PM" src="https://user-images.githubusercontent.com/1151048/94978954-50835580-04dd-11eb-9e08-8e473fc216bf.png"> Changes the card's icon placement and text. Before: <img width="1098" alt="Screen Shot 2020-10-02 at 9 27 44 AM" src="https://user-images.githubusercontent.com/1151048/94979008-9dffc280-04dd-11eb-8bce-88cd49b063a8.png"> After: <img width="1190" alt="Screen Shot 2020-10-02 at 6 31 01 PM" src="https://user-images.githubusercontent.com/1151048/94979005-95a78780-04dd-11eb-92ec-e81bc3ce436e.png"> Fixes the Schedule/Actions and weirdness with CSS. Before: <img width="588" alt="Screen Shot 2020-10-02 at 5 42 09 PM" src="https://user-images.githubusercontent.com/1151048/94979030-c5568f80-04dd-11eb-9c91-683d75dc37da.png"> <img width="516" alt="Screen Shot 2020-10-02 at 5 42 05 PM" src="https://user-images.githubusercontent.com/1151048/94979035-c8ea1680-04dd-11eb-9049-120c9d676b1a.png"> After: <img width="1099" alt="Screen Shot 2020-10-02 at 5 42 51 PM" src="https://user-images.githubusercontent.com/1151048/94979038-d0112480-04dd-11eb-8a2a-8eb314023669.png"> <img width="1067" alt="Screen Shot 2020-10-02 at 5 42 57 PM" src="https://user-images.githubusercontent.com/1151048/94979040-d30c1500-04dd-11eb-85ab-a09e4def6adb.png"> ### Checklist Delete any items that are not applicable to this PR. - [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 was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
8a8066ffe8
commit
6da7dc8695
|
@ -7,33 +7,3 @@ exports[`WrapperPage it renders 1`] = `
|
|||
</p>
|
||||
</Memo(WrapperPageComponent)>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width custom max width when restrictWidth is number 1`] = `
|
||||
<Memo(WrapperPageComponent)
|
||||
restrictWidth={600}
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</Memo(WrapperPageComponent)>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width custom max width when restrictWidth is string 1`] = `
|
||||
<Memo(WrapperPageComponent)
|
||||
restrictWidth="600px"
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</Memo(WrapperPageComponent)>
|
||||
`;
|
||||
|
||||
exports[`WrapperPage restrict width default max width when restrictWidth is true 1`] = `
|
||||
<Memo(WrapperPageComponent)
|
||||
restrictWidth={true}
|
||||
>
|
||||
<p>
|
||||
Test page
|
||||
</p>
|
||||
</Memo(WrapperPageComponent)>
|
||||
`;
|
||||
|
|
|
@ -22,42 +22,4 @@ describe('WrapperPage', () => {
|
|||
|
||||
expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('restrict width', () => {
|
||||
test('default max width when restrictWidth is true', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth>
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('custom max width when restrictWidth is number', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth={600}>
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('custom max width when restrictWidth is string', () => {
|
||||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<WrapperPage restrictWidth="600px">
|
||||
<p>{'Test page'}</p>
|
||||
</WrapperPage>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,16 +16,6 @@ import { AppGlobalStyle } from '../page/index';
|
|||
const Wrapper = styled.div`
|
||||
padding: ${(props) => `${props.theme.eui.paddingSizes.l}`};
|
||||
|
||||
&.siemWrapperPage--restrictWidthDefault,
|
||||
&.siemWrapperPage--restrictWidthCustom {
|
||||
box-sizing: content-box;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.siemWrapperPage--restrictWidthDefault {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
&.siemWrapperPage--fullHeight {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
@ -58,7 +48,6 @@ interface WrapperPageProps {
|
|||
const WrapperPageComponent: React.FC<WrapperPageProps & CommonProps> = ({
|
||||
children,
|
||||
className,
|
||||
restrictWidth,
|
||||
style,
|
||||
noPadding,
|
||||
noTimeline,
|
||||
|
@ -74,20 +63,10 @@ const WrapperPageComponent: React.FC<WrapperPageProps & CommonProps> = ({
|
|||
'siemWrapperPage--noPadding': noPadding,
|
||||
'siemWrapperPage--withTimeline': !noTimeline,
|
||||
'siemWrapperPage--fullHeight': globalFullScreen,
|
||||
'siemWrapperPage--restrictWidthDefault':
|
||||
restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true,
|
||||
'siemWrapperPage--restrictWidthCustom': restrictWidth && typeof restrictWidth !== 'boolean',
|
||||
});
|
||||
|
||||
let customStyle: WrapperPageProps['style'];
|
||||
|
||||
if (restrictWidth && typeof restrictWidth !== 'boolean') {
|
||||
const value = typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth;
|
||||
customStyle = { ...style, maxWidth: value };
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper className={classes} style={customStyle || style} {...otherProps}>
|
||||
<Wrapper className={classes} style={style} {...otherProps}>
|
||||
{children}
|
||||
<AppGlobalStyle />
|
||||
</Wrapper>
|
||||
|
|
|
@ -5,21 +5,235 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import { SelectRuleType } from './index';
|
||||
import { useFormFieldMock } from '../../../../common/mock';
|
||||
import { TestProviders, useFormFieldMock } from '../../../../common/mock';
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
describe('SelectRuleType', () => {
|
||||
// I do this to avoid the messy warning from happening
|
||||
// Warning: React does not recognize the `isVisible` prop on a DOM element.
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.spyOn(console, 'error').mockRestore();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const Component = () => {
|
||||
const field = useFormFieldMock();
|
||||
|
||||
return <SelectRuleType field={field} />;
|
||||
return (
|
||||
<SelectRuleType
|
||||
field={field}
|
||||
describedByIds={[]}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const wrapper = shallow(<Component />);
|
||||
|
||||
expect(wrapper.dive().find('[data-test-subj="selectRuleType"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('update mode vs. non-update mode', () => {
|
||||
it('renders all the cards when not in update mode', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'query' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders only the card selected when in update mode of "eql"', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'eql' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={true}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders only the card selected when in update mode of "machine_learning', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'machine_learning' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={true}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders only the card selected when in update mode of "query', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'query' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={true}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders only the card selected when in update mode of "threshold"', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'threshold' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={true}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeTruthy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders only the card selected when in update mode of "threat_match', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'threat_match' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={true}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="customRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="machineLearningRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="thresholdRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="eqlRuleType"]').exists()).toBeFalsy();
|
||||
expect(wrapper.find('[data-test-subj="threatMatchRuleType"]').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
it('renders machine learning as disabled if "hasValidLicense" is false and it is not selected', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'query' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={false}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="machineLearningRuleType"]').first().prop('isDisabled')
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('renders machine learning as not disabled if "hasValidLicense" is false and it is selected', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'machine_learning' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={false}
|
||||
isMlAdmin={true}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="machineLearningRuleType"]').first().prop('isDisabled')
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('renders machine learning as disabled if "isMlAdmin" is false and it is not selected', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'query' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="machineLearningRuleType"]').first().prop('isDisabled')
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('renders machine learning as not disabled if "isMlAdmin" is false and it is selected', () => {
|
||||
const field = useFormFieldMock<unknown>({ value: 'machine_learning' });
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<SelectRuleType
|
||||
describedByIds={[]}
|
||||
field={field}
|
||||
isUpdateView={false}
|
||||
hasValidLicense={true}
|
||||
isMlAdmin={false}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="machineLearningRuleType"]').first().prop('isDisabled')
|
||||
).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,19 +22,19 @@ import { MlCardDescription } from './ml_card_description';
|
|||
import EqlSearchIcon from './eql_search_icon.svg';
|
||||
|
||||
interface SelectRuleTypeProps {
|
||||
describedByIds?: string[];
|
||||
describedByIds: string[];
|
||||
field: FieldHook;
|
||||
hasValidLicense?: boolean;
|
||||
isMlAdmin?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
hasValidLicense: boolean;
|
||||
isMlAdmin: boolean;
|
||||
isUpdateView: boolean;
|
||||
}
|
||||
|
||||
export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({
|
||||
describedByIds = [],
|
||||
field,
|
||||
isReadOnly = false,
|
||||
hasValidLicense = false,
|
||||
isMlAdmin = false,
|
||||
isUpdateView,
|
||||
hasValidLicense,
|
||||
isMlAdmin,
|
||||
}) => {
|
||||
const ruleType = field.value as Type;
|
||||
const setType = useCallback(
|
||||
|
@ -48,54 +48,54 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({
|
|||
const setQuery = useCallback(() => setType('query'), [setType]);
|
||||
const setThreshold = useCallback(() => setType('threshold'), [setType]);
|
||||
const setThreatMatch = useCallback(() => setType('threat_match'), [setType]);
|
||||
const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin;
|
||||
const licensingUrl = useKibana().services.application.getUrlForApp('kibana', {
|
||||
path: '#/management/stack/license_management',
|
||||
});
|
||||
|
||||
const eqlSelectableConfig = useMemo(
|
||||
() => ({
|
||||
isDisabled: isReadOnly,
|
||||
onClick: setEql,
|
||||
isSelected: isEqlRule(ruleType),
|
||||
isVisible: !isUpdateView || isEqlRule(ruleType),
|
||||
}),
|
||||
[isReadOnly, ruleType, setEql]
|
||||
[ruleType, setEql, isUpdateView]
|
||||
);
|
||||
|
||||
const querySelectableConfig = useMemo(
|
||||
() => ({
|
||||
isDisabled: isReadOnly,
|
||||
onClick: setQuery,
|
||||
isSelected: isQueryRule(ruleType),
|
||||
isVisible: !isUpdateView || isQueryRule(ruleType),
|
||||
}),
|
||||
[isReadOnly, ruleType, setQuery]
|
||||
[ruleType, setQuery, isUpdateView]
|
||||
);
|
||||
|
||||
const mlSelectableConfig = useMemo(
|
||||
() => ({
|
||||
isDisabled: mlCardDisabled,
|
||||
isDisabled: !hasValidLicense || !isMlAdmin,
|
||||
onClick: setMl,
|
||||
isSelected: isMlRule(ruleType),
|
||||
isVisible: !isUpdateView || isMlRule(ruleType),
|
||||
}),
|
||||
[mlCardDisabled, ruleType, setMl]
|
||||
[ruleType, setMl, isUpdateView, hasValidLicense, isMlAdmin]
|
||||
);
|
||||
|
||||
const thresholdSelectableConfig = useMemo(
|
||||
() => ({
|
||||
isDisabled: isReadOnly,
|
||||
onClick: setThreshold,
|
||||
isSelected: isThresholdRule(ruleType),
|
||||
isVisible: !isUpdateView || isThresholdRule(ruleType),
|
||||
}),
|
||||
[isReadOnly, ruleType, setThreshold]
|
||||
[ruleType, setThreshold, isUpdateView]
|
||||
);
|
||||
|
||||
const threatMatchSelectableConfig = useMemo(
|
||||
() => ({
|
||||
isDisabled: isReadOnly,
|
||||
onClick: setThreatMatch,
|
||||
isSelected: isThreatMatchRule(ruleType),
|
||||
isVisible: !isUpdateView || isThreatMatchRule(ruleType),
|
||||
}),
|
||||
[isReadOnly, ruleType, setThreatMatch]
|
||||
[ruleType, setThreatMatch, isUpdateView]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -105,63 +105,83 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({
|
|||
describedByIds={describedByIds}
|
||||
label={field.label}
|
||||
>
|
||||
<EuiFlexGrid columns={4}>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="customRuleType"
|
||||
title={i18n.QUERY_TYPE_TITLE}
|
||||
description={i18n.QUERY_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type="search" />}
|
||||
isDisabled={querySelectableConfig.isDisabled && !querySelectableConfig.isSelected}
|
||||
selectable={querySelectableConfig}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="machineLearningRuleType"
|
||||
title={i18n.ML_TYPE_TITLE}
|
||||
description={
|
||||
<MlCardDescription subscriptionUrl={licensingUrl} hasValidLicense={hasValidLicense} />
|
||||
}
|
||||
icon={<EuiIcon size="l" type="machineLearningApp" />}
|
||||
isDisabled={mlSelectableConfig.isDisabled && !mlSelectableConfig.isSelected}
|
||||
selectable={mlSelectableConfig}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="thresholdRuleType"
|
||||
title={i18n.THRESHOLD_TYPE_TITLE}
|
||||
description={i18n.THRESHOLD_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type="indexFlush" />}
|
||||
isDisabled={
|
||||
thresholdSelectableConfig.isDisabled && !thresholdSelectableConfig.isSelected
|
||||
}
|
||||
selectable={thresholdSelectableConfig}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="eqlRuleType"
|
||||
title={i18n.EQL_TYPE_TITLE}
|
||||
description={i18n.EQL_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type={EqlSearchIcon} />}
|
||||
isDisabled={eqlSelectableConfig.isDisabled && !eqlSelectableConfig.isSelected}
|
||||
selectable={eqlSelectableConfig}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="threatMatchRuleType"
|
||||
title={i18n.THREAT_MATCH_TYPE_TITLE}
|
||||
description={i18n.THREAT_MATCH_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type="list" />}
|
||||
isDisabled={
|
||||
threatMatchSelectableConfig.isDisabled && !threatMatchSelectableConfig.isSelected
|
||||
}
|
||||
selectable={threatMatchSelectableConfig}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGrid columns={3}>
|
||||
{querySelectableConfig.isVisible && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="customRuleType"
|
||||
title={i18n.QUERY_TYPE_TITLE}
|
||||
titleSize="xs"
|
||||
description={i18n.QUERY_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="xl" type="search" />}
|
||||
selectable={querySelectableConfig}
|
||||
layout="horizontal"
|
||||
textAlign="left"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{mlSelectableConfig.isVisible && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="machineLearningRuleType"
|
||||
title={i18n.ML_TYPE_TITLE}
|
||||
titleSize="xs"
|
||||
description={
|
||||
<MlCardDescription
|
||||
subscriptionUrl={licensingUrl}
|
||||
hasValidLicense={hasValidLicense}
|
||||
/>
|
||||
}
|
||||
icon={<EuiIcon size="l" type="machineLearningApp" />}
|
||||
isDisabled={mlSelectableConfig.isDisabled && !mlSelectableConfig.isSelected}
|
||||
selectable={mlSelectableConfig}
|
||||
layout="horizontal"
|
||||
textAlign="left"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{thresholdSelectableConfig.isVisible && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="thresholdRuleType"
|
||||
title={i18n.THRESHOLD_TYPE_TITLE}
|
||||
titleSize="xs"
|
||||
description={i18n.THRESHOLD_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type="indexFlush" />}
|
||||
selectable={thresholdSelectableConfig}
|
||||
layout="horizontal"
|
||||
textAlign="left"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{eqlSelectableConfig.isVisible && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="eqlRuleType"
|
||||
title={i18n.EQL_TYPE_TITLE}
|
||||
titleSize="xs"
|
||||
description={i18n.EQL_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type={EqlSearchIcon} />}
|
||||
selectable={eqlSelectableConfig}
|
||||
layout="horizontal"
|
||||
textAlign="left"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{threatMatchSelectableConfig.isVisible && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
data-test-subj="threatMatchRuleType"
|
||||
title={i18n.THREAT_MATCH_TYPE_TITLE}
|
||||
titleSize="xs"
|
||||
description={i18n.THREAT_MATCH_TYPE_DESCRIPTION}
|
||||
icon={<EuiIcon size="l" type="list" />}
|
||||
selectable={threatMatchSelectableConfig}
|
||||
layout="horizontal"
|
||||
textAlign="left"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGrid>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -236,7 +236,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
component={SelectRuleType}
|
||||
componentProps={{
|
||||
describedByIds: ['detectionEngineStepDefineRuleType'],
|
||||
isReadOnly: isUpdateView,
|
||||
isUpdateView,
|
||||
hasValidLicense: hasMlLicense(mlCapabilities),
|
||||
isMlAdmin: hasMlAdminPermissions(mlCapabilities),
|
||||
}}
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiAccordion,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useRef, useState, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import styled, { StyledComponent } from 'styled-components';
|
||||
|
@ -28,7 +35,12 @@ import { StepScheduleRule } from '../../../../components/rules/step_schedule_rul
|
|||
import { StepRuleActions } from '../../../../components/rules/step_rule_actions';
|
||||
import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page';
|
||||
import * as RuleI18n from '../translations';
|
||||
import { redirectToDetections, getActionMessageParams, userHasNoPermissions } from '../helpers';
|
||||
import {
|
||||
redirectToDetections,
|
||||
getActionMessageParams,
|
||||
userHasNoPermissions,
|
||||
MaxWidthEuiFlexItem,
|
||||
} from '../helpers';
|
||||
import { RuleStep, RuleStepsFormData, RuleStepsFormHooks } from '../types';
|
||||
import { formatRule, stepIsValid } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
@ -273,151 +285,155 @@ const CreateRulePageComponent: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<WrapperPage restrictWidth>
|
||||
<DetectionEngineHeaderPage
|
||||
backOptions={{
|
||||
href: getRulesUrl(),
|
||||
text: i18n.BACK_TO_RULES,
|
||||
pageId: SecurityPageName.detections,
|
||||
}}
|
||||
border
|
||||
isLoading={isLoading || loading}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<MyEuiPanel zindex={4}>
|
||||
<StepDefineRuleAccordion
|
||||
initialIsOpen={true}
|
||||
id={RuleStep.defineRule}
|
||||
buttonContent={defineRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={defineRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.defineRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.defineRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="edit-define-rule"
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.defineRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepDefineRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.defineRule].data}
|
||||
isReadOnlyView={activeStep !== RuleStep.defineRule}
|
||||
<WrapperPage>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceAround">
|
||||
<MaxWidthEuiFlexItem>
|
||||
<DetectionEngineHeaderPage
|
||||
backOptions={{
|
||||
href: getRulesUrl(),
|
||||
text: i18n.BACK_TO_RULES,
|
||||
pageId: SecurityPageName.detections,
|
||||
}}
|
||||
border
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.defineRule)}
|
||||
descriptionColumns="singleSplit"
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
</StepDefineRuleAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={3}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.aboutRule}
|
||||
buttonContent={aboutRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={aboutRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.aboutRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.aboutRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="edit-about-rule"
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.aboutRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.aboutRule].data}
|
||||
defineRuleData={stepsData.current[RuleStep.defineRule].data}
|
||||
descriptionColumns="singleSplit"
|
||||
isReadOnlyView={activeStep !== RuleStep.aboutRule}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.aboutRule)}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={2}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.scheduleRule}
|
||||
buttonContent={scheduleRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={scheduleRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.scheduleRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.scheduleRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.scheduleRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepScheduleRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.scheduleRule].data}
|
||||
descriptionColumns="singleSplit"
|
||||
isReadOnlyView={activeStep !== RuleStep.scheduleRule}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.scheduleRule)}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={1}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.ruleActions}
|
||||
buttonContent={ruleActionsButton}
|
||||
paddingSize="xs"
|
||||
ref={ruleActionsRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.ruleActions)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.ruleActions].isValid && (
|
||||
<EuiButtonEmpty
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.ruleActions)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepRuleActions
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.ruleActions].data}
|
||||
isReadOnlyView={activeStep !== RuleStep.ruleActions}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.ruleActions)}
|
||||
actionMessageParams={actionMessageParams}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<MyEuiPanel zindex={4}>
|
||||
<StepDefineRuleAccordion
|
||||
initialIsOpen={true}
|
||||
id={RuleStep.defineRule}
|
||||
buttonContent={defineRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={defineRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.defineRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.defineRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="edit-define-rule"
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.defineRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepDefineRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.defineRule].data}
|
||||
isReadOnlyView={activeStep !== RuleStep.defineRule}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.defineRule)}
|
||||
descriptionColumns="singleSplit"
|
||||
/>
|
||||
</StepDefineRuleAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={3}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.aboutRule}
|
||||
buttonContent={aboutRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={aboutRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.aboutRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.aboutRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="edit-about-rule"
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.aboutRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.aboutRule].data}
|
||||
defineRuleData={stepsData.current[RuleStep.defineRule].data}
|
||||
descriptionColumns="singleSplit"
|
||||
isReadOnlyView={activeStep !== RuleStep.aboutRule}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.aboutRule)}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={2}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.scheduleRule}
|
||||
buttonContent={scheduleRuleButton}
|
||||
paddingSize="xs"
|
||||
ref={scheduleRuleRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.scheduleRule)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.scheduleRule].isValid && (
|
||||
<EuiButtonEmpty
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.scheduleRule)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepScheduleRule
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.scheduleRule].data}
|
||||
descriptionColumns="singleSplit"
|
||||
isReadOnlyView={activeStep !== RuleStep.scheduleRule}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.scheduleRule)}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zindex={1}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.ruleActions}
|
||||
buttonContent={ruleActionsButton}
|
||||
paddingSize="xs"
|
||||
ref={ruleActionsRef}
|
||||
onToggle={handleAccordionToggle.bind(null, RuleStep.ruleActions)}
|
||||
extraAction={
|
||||
stepsData.current[RuleStep.ruleActions].isValid && (
|
||||
<EuiButtonEmpty
|
||||
iconType="pencil"
|
||||
size="xs"
|
||||
onClick={() => editStep(RuleStep.ruleActions)}
|
||||
>
|
||||
{i18n.EDIT_RULE}
|
||||
</EuiButtonEmpty>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepRuleActions
|
||||
addPadding={true}
|
||||
defaultValues={stepsData.current[RuleStep.ruleActions].data}
|
||||
isReadOnlyView={activeStep !== RuleStep.ruleActions}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setFormHook}
|
||||
onSubmit={() => submitStep(RuleStep.ruleActions)}
|
||||
actionMessageParams={actionMessageParams}
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
</MaxWidthEuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</WrapperPage>
|
||||
|
||||
<SpyRoute pageName={SecurityPageName.detections} />
|
||||
|
|
|
@ -47,6 +47,7 @@ import {
|
|||
redirectToDetections,
|
||||
getActionMessageParams,
|
||||
userHasNoPermissions,
|
||||
MaxWidthEuiFlexItem,
|
||||
} from '../helpers';
|
||||
import * as ruleI18n from '../translations';
|
||||
import { RuleStep, RuleStepsFormHooks, RuleStepsFormData, RuleStepsData } from '../types';
|
||||
|
@ -332,75 +333,79 @@ const EditRulePageComponent: FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<WrapperPage restrictWidth>
|
||||
<DetectionEngineHeaderPage
|
||||
backOptions={{
|
||||
href: getRuleDetailsUrl(ruleId ?? ''),
|
||||
text: `${i18n.BACK_TO} ${rule?.name ?? ''}`,
|
||||
pageId: SecurityPageName.detections,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
{invalidSteps.length > 0 && (
|
||||
<EuiCallOut title={i18n.SORRY_ERRORS} color="danger" iconType="alert">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rule.editRule.errorMsgDescription"
|
||||
defaultMessage="You have an invalid input in {countError, plural, one {this tab} other {these tabs}}: {tabHasError}"
|
||||
values={{
|
||||
countError: invalidSteps.length,
|
||||
tabHasError: invalidSteps
|
||||
.map((t) => {
|
||||
if (t === RuleStep.aboutRule) {
|
||||
return ruleI18n.ABOUT;
|
||||
} else if (t === RuleStep.defineRule) {
|
||||
return ruleI18n.DEFINITION;
|
||||
} else if (t === RuleStep.scheduleRule) {
|
||||
return ruleI18n.SCHEDULE;
|
||||
} else if (t === RuleStep.ruleActions) {
|
||||
return ruleI18n.RULE_ACTIONS;
|
||||
}
|
||||
return t;
|
||||
})
|
||||
.join(', '),
|
||||
<WrapperPage>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceAround">
|
||||
<MaxWidthEuiFlexItem>
|
||||
<DetectionEngineHeaderPage
|
||||
backOptions={{
|
||||
href: getRuleDetailsUrl(ruleId ?? ''),
|
||||
text: `${i18n.BACK_TO} ${rule?.name ?? ''}`,
|
||||
pageId: SecurityPageName.detections,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
|
||||
<EuiTabbedContent
|
||||
initialSelectedTab={tabs[0]}
|
||||
selectedTab={tabs.find((t) => t.id === activeStep)}
|
||||
onTabClick={onTabClick}
|
||||
tabs={tabs}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
justifyContent="flexEnd"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="cross" onClick={goToDetailsRule}>
|
||||
{i18n.CANCEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="ruleEditSubmitButton"
|
||||
fill
|
||||
onClick={onSubmit}
|
||||
iconType="save"
|
||||
isLoading={isLoading}
|
||||
isDisabled={loading}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
{invalidSteps.length > 0 && (
|
||||
<EuiCallOut title={i18n.SORRY_ERRORS} color="danger" iconType="alert">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rule.editRule.errorMsgDescription"
|
||||
defaultMessage="You have an invalid input in {countError, plural, one {this tab} other {these tabs}}: {tabHasError}"
|
||||
values={{
|
||||
countError: invalidSteps.length,
|
||||
tabHasError: invalidSteps
|
||||
.map((t) => {
|
||||
if (t === RuleStep.aboutRule) {
|
||||
return ruleI18n.ABOUT;
|
||||
} else if (t === RuleStep.defineRule) {
|
||||
return ruleI18n.DEFINITION;
|
||||
} else if (t === RuleStep.scheduleRule) {
|
||||
return ruleI18n.SCHEDULE;
|
||||
} else if (t === RuleStep.ruleActions) {
|
||||
return ruleI18n.RULE_ACTIONS;
|
||||
}
|
||||
return t;
|
||||
})
|
||||
.join(', '),
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
)}
|
||||
|
||||
<EuiTabbedContent
|
||||
initialSelectedTab={tabs[0]}
|
||||
selectedTab={tabs.find((t) => t.id === activeStep)}
|
||||
onTabClick={onTabClick}
|
||||
tabs={tabs}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
justifyContent="flexEnd"
|
||||
responsive={false}
|
||||
>
|
||||
{i18n.SAVE_CHANGES}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="cross" onClick={goToDetailsRule}>
|
||||
{i18n.CANCEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="ruleEditSubmitButton"
|
||||
fill
|
||||
onClick={onSubmit}
|
||||
iconType="save"
|
||||
isLoading={isLoading}
|
||||
isDisabled={loading}
|
||||
>
|
||||
{i18n.SAVE_CHANGES}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</MaxWidthEuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</WrapperPage>
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import moment from 'moment';
|
|||
import memoizeOne from 'memoize-one';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { ActionVariable } from '../../../../../../triggers_actions_ui/public';
|
||||
import { RuleAlertAction } from '../../../../../common/detection_engine/types';
|
||||
import { assertUnreachable } from '../../../../../common/utility_types';
|
||||
|
@ -377,3 +379,8 @@ export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): A
|
|||
// typed as null not undefined as the initial state for this value is null.
|
||||
export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean =>
|
||||
canUserCRUD != null ? !canUserCRUD : false;
|
||||
|
||||
export const MaxWidthEuiFlexItem = styled(EuiFlexItem)`
|
||||
max-width: 1000px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
|
Loading…
Reference in a new issue