diff --git a/cypress/integration/2_rules.spec.js b/cypress/integration/2_rules.spec.js index 05bf9c77f..921534e57 100644 --- a/cypress/integration/2_rules.spec.js +++ b/cypress/integration/2_rules.spec.js @@ -70,16 +70,13 @@ describe('Rules', () => { cy.get('[data-test-subj="rule_name_field"]').type(SAMPLE_RULE.name); // Enter the log type - cy.get('[data-test-subj="rule_type_dropdown"]').select(SAMPLE_RULE.logType); + cy.get('[data-test-subj="rule_type_dropdown"]').type(SAMPLE_RULE.logType); // Enter the description cy.get('[data-test-subj="rule_description_field"]').type(SAMPLE_RULE.description); - // Enter the detection - cy.get('[data-test-subj="rule_detection_field"]').type(SAMPLE_RULE.detection); - // Enter the severity - cy.get('[data-test-subj="rule_severity_dropdown"]').select(SAMPLE_RULE.severity); + cy.get('[data-test-subj="rule_severity_dropdown"]').type(SAMPLE_RULE.severity); // Enter the tags SAMPLE_RULE.tags.forEach((tag) => @@ -87,16 +84,21 @@ describe('Rules', () => { ); // Enter the reference - cy.get('[data-test-subj="rule_references_field_0"]').type(SAMPLE_RULE.references); + cy.get('[data-test-subj="rule_references_-_optional_field_0"]').type(SAMPLE_RULE.references); // Enter the false positive cases - cy.get('[data-test-subj="rule_false_positive_cases_field_0"]').type(SAMPLE_RULE.falsePositive); + cy.get('[data-test-subj="rule_false_positive_cases_-_optional_field_0"]').type( + SAMPLE_RULE.falsePositive + ); // Enter the author cy.get('[data-test-subj="rule_author_field"]').type(SAMPLE_RULE.author); // Enter the log type - cy.get('[data-test-subj="rule_status_dropdown"]').select(SAMPLE_RULE.status); + cy.get('[data-test-subj="rule_status_dropdown"]').type(SAMPLE_RULE.status); + + // Enter the detection + cy.get('[data-test-subj="rule_detection_field"]').type(SAMPLE_RULE.detection); // Switch to YAML editor cy.get('[data-test-subj="change-editor-type"] label:nth-child(2)').click({ @@ -110,7 +112,7 @@ describe('Rules', () => { }).as('getRules'); // Click "create" button - cy.get('[data-test-subj="create_rule_button"]').click({ + cy.get('[data-test-subj="submit_rule_form_button"]').click({ force: true, }); diff --git a/package.json b/package.json index 9a31bd04f..ae065b02b 100644 --- a/package.json +++ b/package.json @@ -65,5 +65,8 @@ }, "engines": { "yarn": "^1.21.1" + }, + "dependencies": { + "formik": "^2.2.9" } } diff --git a/public/pages/Rules/components/RuleEditor/FormSubmitionErrorToastNotification.tsx b/public/pages/Rules/components/RuleEditor/FormSubmitionErrorToastNotification.tsx new file mode 100644 index 000000000..f664629e0 --- /dev/null +++ b/public/pages/Rules/components/RuleEditor/FormSubmitionErrorToastNotification.tsx @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { useEffect, useState } from 'react'; +import { useFormikContext } from 'formik'; +import { NotificationsStart } from 'opensearch-dashboards/public'; +import { errorNotificationToast } from '../../../../utils/helpers'; + +export const FormSubmitionErrorToastNotification = ({ + notifications, +}: { + notifications?: NotificationsStart; +}) => { + const { submitCount, isValid } = useFormikContext(); + const [prevSubmitCount, setPrevSubmitCount] = useState(submitCount); + + useEffect(() => { + if (isValid) return; + + if (submitCount === prevSubmitCount) return; + + setPrevSubmitCount(submitCount); + + errorNotificationToast( + notifications!, + 'create', + 'rule', + 'Some fields are invalid. Fix all highlighted error(s) before continuing.' + ); + }, [submitCount, isValid]); + return null; +}; diff --git a/public/pages/Rules/components/RuleEditor/RuleEditor.tsx b/public/pages/Rules/components/RuleEditor/RuleEditor.tsx index 6eab679e8..7fbdb55aa 100644 --- a/public/pages/Rules/components/RuleEditor/RuleEditor.tsx +++ b/public/pages/Rules/components/RuleEditor/RuleEditor.tsx @@ -3,7 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import { NotificationsStart } from 'opensearch-dashboards/public'; +import { RuleService } from '../../../../services'; +import { ROUTES } from '../../../../utils/constants'; import { ContentPanel } from '../../../../components/ContentPanel'; import { EuiSpacer, EuiButtonGroup } from '@elastic/eui'; import { Rule } from '../../../../../models/interfaces'; @@ -11,11 +15,16 @@ import { RuleEditorFormState, ruleEditorStateDefaultValue } from './RuleEditorFo import { mapFormToRule, mapRuleToForm } from './mappers'; import { VisualRuleEditor } from './VisualRuleEditor'; import { YamlRuleEditor } from './YamlRuleEditor'; +import { validateRule } from '../../utils/helpers'; +import { errorNotificationToast } from '../../../../utils/helpers'; export interface RuleEditorProps { title: string; - FooterActions: React.FC<{ rule: Rule }>; rule?: Rule; + history: RouteComponentProps['history']; + notifications?: NotificationsStart; + ruleService: RuleService; + mode: 'create' | 'edit'; } export interface VisualEditorFormErrorsState { @@ -35,7 +44,14 @@ const editorTypes = [ }, ]; -export const RuleEditor: React.FC = ({ title, rule, FooterActions }) => { +export const RuleEditor: React.FC = ({ + history, + notifications, + title, + rule, + ruleService, + mode, +}) => { const [ruleEditorFormState, setRuleEditorFormState] = useState( rule ? { ...mapRuleToForm(rule), id: ruleEditorStateDefaultValue.id } @@ -48,15 +64,44 @@ export const RuleEditor: React.FC = ({ title, rule, FooterActio setSelectedEditorType(optionId); }; - const getRule = (): Rule => { - return mapFormToRule(ruleEditorFormState); - }; - const onYamlRuleEditorChange = (value: Rule) => { const formState = mapRuleToForm(value); setRuleEditorFormState(formState); }; + const onSubmit = async () => { + const submitingRule = mapFormToRule(ruleEditorFormState); + if (!validateRule(submitingRule, notifications!, 'create')) { + return; + } + + let result; + if (mode === 'edit') { + if (!rule) { + console.error('No rule id found'); + return; + } + result = await ruleService.updateRule(rule?.id, submitingRule.category, submitingRule); + } else { + result = await ruleService.createRule(submitingRule); + } + + if (!result.ok) { + errorNotificationToast( + notifications!, + mode === 'create' ? 'create' : 'save', + 'rule', + result.error + ); + } else { + history.replace(ROUTES.RULES); + } + }; + + const goToRulesList = useCallback(() => { + history.replace(ROUTES.RULES); + }, [history]); + return ( <> @@ -70,20 +115,26 @@ export const RuleEditor: React.FC = ({ title, rule, FooterActio {selectedEditorType === 'visual' && ( )} {selectedEditorType === 'yaml' && ( )} - ); }; diff --git a/public/pages/Rules/components/RuleEditor/VisualRuleEditor.tsx b/public/pages/Rules/components/RuleEditor/VisualRuleEditor.tsx index 03126ab52..5708eb1c1 100644 --- a/public/pages/Rules/components/RuleEditor/VisualRuleEditor.tsx +++ b/public/pages/Rules/components/RuleEditor/VisualRuleEditor.tsx @@ -3,18 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { ChangeEvent, useState } from 'react'; +import React from 'react'; +import { Formik, Form, FormikErrors } from 'formik'; +import { NotificationsStart } from 'opensearch-dashboards/public'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldText, - EuiSelect, + EuiButton, EuiSpacer, EuiTextArea, EuiComboBox, EuiCodeEditor, - EuiComboBoxOptionOption, } from '@elastic/eui'; import { FieldTextArray } from './FieldTextArray'; import { ruleStatus, ruleTypes } from '../../utils/constants'; @@ -27,292 +28,358 @@ import { validateName, } from '../../../../utils/validation'; import { RuleEditorFormState } from './RuleEditorFormState'; +import { FormSubmitionErrorToastNotification } from './FormSubmitionErrorToastNotification'; export interface VisualRuleEditorProps { ruleEditorFormState: RuleEditorFormState; setRuleEditorFormState: React.Dispatch>; -} - -export interface VisualEditorFormErrorsState { - nameError: string | null; - descriptionError: string | null; - authorError: string | null; + notifications?: NotificationsStart; + submit: () => void; + cancel: () => void; + mode: 'create' | 'edit'; } export const VisualRuleEditor: React.FC = ({ ruleEditorFormState, setRuleEditorFormState, + notifications, + submit, + cancel, + mode, }) => { - const [visualEditorErrors, setVisualEditorErrors] = useState({ - nameError: null, - descriptionError: null, - authorError: null, - }); + return ( + { + const errors: FormikErrors = {}; - const onNameChange = (e: ChangeEvent) => { - const { value: name } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, name })); - }; - const onNameBlur = (e: ChangeEvent) => { - if (!validateName(e.target.value)) { - setVisualEditorErrors((prevState) => ({ ...prevState, nameError: nameErrorString })); - } else { - setVisualEditorErrors((prevState) => ({ ...prevState, nameError: null })); - } - }; + if (!values.name) { + errors.name = 'Rule name is required'; + } else { + if (!validateName(values.name)) { + errors.name = nameErrorString; + } + } - const onLogTypeChange = (e: ChangeEvent) => { - const { value: logType } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, logType })); - }; + if (!validateDescription(values.description)) { + errors.description = descriptionErrorString; + } - const onDescriptionChange = (e: ChangeEvent) => { - const { value: description } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, description })); - }; - const onDescriptionBlur = (e: ChangeEvent) => { - if (!validateDescription(e.target.value)) { - setVisualEditorErrors((prevState) => ({ - ...prevState, - descriptionError: descriptionErrorString, - })); - } else { - setVisualEditorErrors((prevState) => ({ ...prevState, descriptionError: null })); - } - }; + if (!values.logType) { + errors.logType = 'Log type is required'; + } - const onLevelChange = (e: ChangeEvent) => { - const { value: level } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, level })); - }; + if (!values.detection) { + errors.detection = 'Detection is required'; + } - const onTagsChange = (selectedOptions: EuiComboBoxOptionOption[]) => { - const tags = selectedOptions.map((option) => ({ label: option.label })); - setRuleEditorFormState((prevState) => ({ ...prevState, tags })); - }; - const onCreateTag = (value: string) => { - setRuleEditorFormState((prevState) => ({ - ...prevState, - tags: [...prevState.tags, { label: value }], - })); - }; + if (!values.level) { + errors.level = 'Rule level is required'; + } - const onAuthorChange = (e: ChangeEvent) => { - const { value: author } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, author })); - }; + if (!values.author) { + errors.author = 'Author name is required'; + } else { + if (!validateName(values.author, AUTHOR_REGEX)) { + errors.author = authorErrorString; + } + } - const onAuthorBlur = (e: ChangeEvent) => { - if (!validateName(e.target.value, AUTHOR_REGEX)) { - setVisualEditorErrors((prevState) => ({ ...prevState, authorError: authorErrorString })); - } else { - setVisualEditorErrors((prevState) => ({ ...prevState, authorError: null })); - } - }; + if (!values.status) { + errors.status = 'Rule status is required'; + } - const onStatusChange = (e: ChangeEvent) => { - const { value: status } = e.target; - setRuleEditorFormState((prevState) => ({ ...prevState, status })); - }; + return errors; + }} + onSubmit={(values, { setSubmitting }) => { + setSubmitting(false); + submit(); + }} + > + {(props) => ( +
+ + + + + { + props.handleChange('name')(e); + setRuleEditorFormState({ ...props.values, name: e.target.value }); + }} + onBlur={props.handleBlur('name')} + value={props.values.name} + /> + + + + + ({ value: type, label: type }))} + singleSelection={{ asPlainText: true }} + onChange={(e) => { + props.handleChange('logType')(e[0]?.value ? e[0].value : ''); + setRuleEditorFormState({ ...props.values, logType: e[0]?.value || '' }); + }} + onBlur={props.handleBlur('logType')} + selectedOptions={ + props.values.logType + ? [{ value: props.values.logType, label: props.values.logType }] + : [] + } + /> + + + - const onDetectionChange = (value: string) => { - setRuleEditorFormState((prevState) => ({ ...prevState, detection: value })); - }; + - const onReferenceAdd = () => { - setRuleEditorFormState((prevState) => ({ - ...prevState, - references: [...prevState.references, ''], - })); - }; - const onReferenceEdit = (value: string, index: number) => { - setRuleEditorFormState((prevState) => ({ - ...prevState, - references: [ - ...prevState.references.slice(0, index), - value, - ...prevState.references.slice(index + 1), - ], - })); - }; - const onReferenceRemove = (index: number) => { - setRuleEditorFormState((prevState) => { - const newRefs = [...prevState.references]; - newRefs.splice(index, 1); - return { - ...prevState, - references: newRefs, - }; - }); - }; + + { + props.handleChange('description')(e.target.value); + setRuleEditorFormState({ ...props.values, description: e.target.value }); + }} + onBlur={props.handleBlur('description')} + value={props.values.description} + /> + - const onFalsePositiveAdd = () => { - setRuleEditorFormState((prevState) => ({ - ...prevState, - falsePositives: [...prevState.falsePositives, ''], - })); - }; - const onFalsePositiveEdit = (value: string, index: number) => { - setRuleEditorFormState((prevState) => ({ - ...prevState, - falsePositives: [ - ...prevState.falsePositives.slice(0, index), - value, - ...prevState.falsePositives.slice(index + 1), - ], - })); - }; - const onFalsePositiveRemove = (index: number) => { - setRuleEditorFormState((prevState) => { - const newFalsePositives = [...prevState.falsePositives]; - newFalsePositives.splice(index, 1); - return { - ...prevState, - falsePositives: newFalsePositives, - }; - }); - }; + - return ( - <> - - - { + props.handleChange('detection')(value); + setRuleEditorFormState({ ...props.values, detection: value }); + }} + onBlur={props.handleBlur('detection')} + data-test-subj={'rule_detection_field'} /> - - - - ({ value: type, text: type }))} - onChange={onLogTypeChange} - value={ruleEditorFormState.logType} - required - data-test-subj={'rule_type_dropdown'} + + + + { + props.handleChange('level')(e[0]?.value ? e[0].value : ''); + setRuleEditorFormState({ ...props.values, level: e[0]?.value || '' }); + }} + onBlur={props.handleBlur('level')} + selectedOptions={ + props.values.level ? [{ value: props.values.level, label: props.values.level }] : [] + } /> - - - - - - - + - - - - - - + + { + const tags = value.map((option) => ({ label: option.label })); + props.setFieldValue('tags', tags); + setRuleEditorFormState({ + ...props.values, + tags, + }); + }} + onCreateOption={(newTag) => { + props.setFieldValue('tags', [...props.values.tags, { label: newTag }]); + setRuleEditorFormState((prevState) => ({ + ...prevState, + tags: [...prevState.tags, { label: newTag }], + })); + }} + onBlur={props.handleBlur('tags')} + data-test-subj={'rule_tags_dropdown'} + selectedOptions={props.values.tags} + /> + - - - + + { + props.setFieldValue('references', [...props.values.references, '']); + setRuleEditorFormState((prevState) => ({ + ...prevState, + references: [...prevState.references, ''], + })); + }} + onFieldEdit={(value: string, index: number) => { + props.setFieldValue('references', [ + ...props.values.references.slice(0, index), + value, + ...props.values.references.slice(index + 1), + ]); + setRuleEditorFormState((prevState) => ({ + ...prevState, + references: [ + ...prevState.references.slice(0, index), + value, + ...prevState.references.slice(index + 1), + ], + })); + }} + onFieldRemove={(index: number) => { + const newRefs = [...props.values.references]; + newRefs.splice(index, 1); - + props.setFieldValue('references', newRefs); + setRuleEditorFormState((prevState) => ({ + ...prevState, + references: newRefs, + })); + }} + data-test-subj={'rule_references_field'} + /> - - - + { + props.setFieldValue('falsePositives', [...props.values.falsePositives, '']); + setRuleEditorFormState((prevState) => ({ + ...prevState, + falsePositives: [...prevState.falsePositives, ''], + })); + }} + onFieldEdit={(value: string, index: number) => { + props.setFieldValue('falsePositives', [ + ...props.values.falsePositives.slice(0, index), + value, + ...props.values.falsePositives.slice(index + 1), + ]); + setRuleEditorFormState((prevState) => ({ + ...prevState, + falsePositives: [ + ...prevState.falsePositives.slice(0, index), + value, + ...prevState.falsePositives.slice(index + 1), + ], + })); + }} + onFieldRemove={(index: number) => { + const newCases = [...props.values.falsePositives]; + newCases.splice(index, 1); - - + props.setFieldValue('falsePositives', newCases); + setRuleEditorFormState((prevState) => ({ + ...prevState, + falsePositives: newCases, + })); + }} + data-test-subj={'rule_falsePositives_field'} + /> - + + { + props.handleChange('author')(e); + setRuleEditorFormState({ ...props.values, author: e.target.value }); + }} + onBlur={props.handleBlur('author')} + value={props.values.author} + /> + - - - + - + + ({ value: type, label: type }))} + singleSelection={{ asPlainText: true }} + onChange={(e) => { + props.handleChange('status')(e[0]?.value ? e[0].value : ''); + setRuleEditorFormState({ ...props.values, status: e[0]?.value || '' }); + }} + onBlur={props.handleBlur('status')} + selectedOptions={ + props.values.status + ? [{ value: props.values.status, label: props.values.status }] + : [] + } + /> + - - ({ value: status, text: status }))} - onChange={onStatusChange} - value={ruleEditorFormState.status} - required - data-test-subj={'rule_status_dropdown'} - /> - + - - + + + Cancel + + + props.handleSubmit()} + data-test-subj={'submit_rule_form_button'} + fill + > + {mode === 'create' ? 'Create' : 'Save changes'} + + + + + )} +
); }; diff --git a/public/pages/Rules/components/RuleEditor/YamlRuleEditor.tsx b/public/pages/Rules/components/RuleEditor/YamlRuleEditor.tsx index 842faffef..a1033dea7 100644 --- a/public/pages/Rules/components/RuleEditor/YamlRuleEditor.tsx +++ b/public/pages/Rules/components/RuleEditor/YamlRuleEditor.tsx @@ -5,7 +5,17 @@ import React, { useState } from 'react'; import { load } from 'js-yaml'; -import { EuiFormRow, EuiCodeEditor, EuiLink, EuiSpacer, EuiText, EuiForm } from '@elastic/eui'; +import { + EuiFormRow, + EuiCodeEditor, + EuiLink, + EuiSpacer, + EuiText, + EuiForm, + EuiFlexGroup, + EuiButton, + EuiFlexItem, +} from '@elastic/eui'; import FormFieldHeader from '../../../../components/FormFieldHeader'; import { Rule } from '../../../../../models/interfaces'; import { @@ -25,6 +35,9 @@ import { export interface YamlRuleEditorProps { rule: Rule; change: React.Dispatch; + submit: () => void; + cancel: () => void; + mode: 'create' | 'edit'; } export interface YamlEditorState { @@ -68,7 +81,13 @@ const validateRule = (rule: Rule): string[] | null => { return null; }; -export const YamlRuleEditor: React.FC = ({ rule, change }) => { +export const YamlRuleEditor: React.FC = ({ + rule, + change, + submit, + cancel, + mode, +}) => { const yamlObject = mapRuleToYamlObject(rule); const [state, setState] = useState({ @@ -90,6 +109,8 @@ export const YamlRuleEditor: React.FC = ({ rule, change }) const rule = mapYamlObjectToRule(yamlObject); + change(rule); + const errors = validateRule(rule); if (errors && errors.length > 0) { @@ -97,7 +118,6 @@ export const YamlRuleEditor: React.FC = ({ rule, change }) return; } - change(rule); setState((prevState) => ({ ...prevState, errors: null })); } catch (error) { setState((prevState) => ({ ...prevState, errors: ['Invalid YAML'] })); @@ -136,6 +156,19 @@ export const YamlRuleEditor: React.FC = ({ rule, change }) /> + + + + + + Cancel + + + + {mode === 'create' ? 'Create' : 'Save changes'} + + + ); diff --git a/public/pages/Rules/components/RuleEditor/__snapshots__/YamlRuleEditor.test.tsx.snap b/public/pages/Rules/components/RuleEditor/__snapshots__/YamlRuleEditor.test.tsx.snap index ec1223fd0..32142ba87 100644 --- a/public/pages/Rules/components/RuleEditor/__snapshots__/YamlRuleEditor.test.tsx.snap +++ b/public/pages/Rules/components/RuleEditor/__snapshots__/YamlRuleEditor.test.tsx.snap @@ -162,5 +162,49 @@ exports[` spec renders the component 1`] = ` +
+
+
+ +
+
+ +
+
`; diff --git a/public/pages/Rules/containers/CreateRule/CreateRule.tsx b/public/pages/Rules/containers/CreateRule/CreateRule.tsx index f694a7ee0..5fb2fa962 100644 --- a/public/pages/Rules/containers/CreateRule/CreateRule.tsx +++ b/public/pages/Rules/containers/CreateRule/CreateRule.tsx @@ -6,14 +6,11 @@ import { BrowserServices } from '../../../../models/interfaces'; import { RuleEditor } from '../../components/RuleEditor/RuleEditor'; import React, { useContext } from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { RouteComponentProps } from 'react-router-dom'; -import { BREADCRUMBS, ROUTES } from '../../../../utils/constants'; -import { Rule } from '../../../../../models/interfaces'; +import { BREADCRUMBS } from '../../../../utils/constants'; import { CoreServicesContext } from '../../../../components/core_services'; import { NotificationsStart } from 'opensearch-dashboards/public'; -import { errorNotificationToast } from '../../../../utils/helpers'; -import { setBreadCrumb, validateRule } from '../../utils/helpers'; +import { setBreadCrumb } from '../../utils/helpers'; export interface CreateRuleProps { services: BrowserServices; @@ -24,33 +21,14 @@ export interface CreateRuleProps { export const CreateRule: React.FC = ({ history, services, notifications }) => { const context = useContext(CoreServicesContext); setBreadCrumb(BREADCRUMBS.RULES_CREATE, context?.chrome.setBreadcrumbs); - const footerActions: React.FC<{ rule: Rule }> = ({ rule }) => { - const onCreate = async () => { - if (!validateRule(rule, notifications!, 'create')) { - return; - } - const createRuleRes = await services.ruleService.createRule(rule); - if (!createRuleRes.ok) { - errorNotificationToast(notifications!, 'create', 'rule', createRuleRes.error); - } else { - history.replace(ROUTES.RULES); - } - }; - - return ( - - - history.replace(ROUTES.RULES)}>Cancel - - - - Create - - - - ); - }; - - return ; + return ( + + ); }; diff --git a/public/pages/Rules/containers/EditRule/EditRule.tsx b/public/pages/Rules/containers/EditRule/EditRule.tsx index f9b788cdc..ca4a92f90 100644 --- a/public/pages/Rules/containers/EditRule/EditRule.tsx +++ b/public/pages/Rules/containers/EditRule/EditRule.tsx @@ -30,44 +30,15 @@ export const EditRule: React.FC = ({ }) => { const context = useContext(CoreServicesContext); setBreadCrumb(BREADCRUMBS.RULES_EDIT, context?.chrome.setBreadcrumbs); - const footerActions: React.FC<{ rule: Rule }> = ({ rule }) => { - const onSave = async () => { - if (!validateRule(rule, notifications!, 'save')) { - return; - } - - const editRuleRes = await services.ruleService.updateRule( - location.state.ruleItem._id, - rule.category, - rule - ); - - if (!editRuleRes.ok) { - errorNotificationToast(notifications!, 'save', 'rule', editRuleRes.error); - } else { - history.replace(ROUTES.RULES); - } - }; - - return ( - - - history.replace(ROUTES.RULES)}>Cancel - - - - Save changes - - - - ); - }; return ( ); }; diff --git a/public/pages/Rules/utils/constants.ts b/public/pages/Rules/utils/constants.ts index d4acf1d2d..70c3ead2e 100644 --- a/public/pages/Rules/utils/constants.ts +++ b/public/pages/Rules/utils/constants.ts @@ -24,4 +24,4 @@ export const ruleSeverity: { name: string; value: string }[] = [ export const ruleSource: string[] = ['Sigma', 'Custom']; -export const ruleStatus: string[] = ['Select a rule status', 'experimental', 'test', 'stable']; +export const ruleStatus: string[] = ['experimental', 'test', 'stable']; diff --git a/yarn.lock b/yarn.lock index 604b135a9..d30a1c08e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2166,6 +2166,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -2795,6 +2800,19 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formik@^2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.10.0" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3055,6 +3073,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4, hosted-git-info@^2.8.9: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -4163,6 +4188,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -5034,7 +5064,12 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -react-is@^16.8.4: +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-is@^16.7.0, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -5800,6 +5835,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -5890,7 +5930,7 @@ ts-loader@^6.2.1: micromatch "^4.0.0" semver "^6.0.0" -tslib@^1.9.0: +tslib@^1.10.0, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==