Skip to content

Commit

Permalink
fix validation upon saving
Browse files Browse the repository at this point in the history
  • Loading branch information
maximpn committed Nov 27, 2024
1 parent 696b1a5 commit f9e8e69
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export * from './threat_match_mapping_edit';
export { DEFAULT_THREAT_MAPPING_VALUE } from './threat_match_mapping_field';
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type { DataViewBase } from '@kbn/es-query';
import type { ThreatMapEntries } from '../../../../common/components/threat_match/types';
import type { FieldConfig } from '../../../../shared_imports';
import { UseField } from '../../../../shared_imports';
import { threatMatchMappingValidatorFactory } from '../../../rule_creation_ui/validators/threat_match_mapping_validator';
import { ThreatMatchField } from './threat_match_mapping_field';
import { threatMatchMappingValidator } from './validators/threat_match_mapping_validator';
import { ThreatMatchMappingField } from './threat_match_mapping_field';
import * as i18n from './translations';

interface ThreatMatchMappingEditProps {
Expand All @@ -29,7 +29,7 @@ export const ThreatMatchMappingEdit = memo(function ThreatMatchMappingEdit({
<UseField
path={path}
config={THREAT_MATCH_MAPPING_FIELD_CONFIG}
component={ThreatMatchField}
component={ThreatMatchMappingField}
componentProps={{
indexPatterns,
threatIndexPatterns,
Expand All @@ -42,7 +42,7 @@ const THREAT_MATCH_MAPPING_FIELD_CONFIG: FieldConfig<ThreatMapEntries[]> = {
label: i18n.THREAT_MATCH_MAPPING_FIELD_LABEL,
validations: [
{
validator: threatMatchMappingValidatorFactory(),
validator: threatMatchMappingValidator,
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useCallback, useEffect } from 'react';
import React, { useCallback } from 'react';
import { EuiFormRow } from '@elastic/eui';
import type { DataViewBase } from '@kbn/es-query';
import { createOrNewEntryItem } from '../../../../common/components/threat_match/helpers';
Expand All @@ -14,24 +14,26 @@ import { ThreatMatchComponent } from '../../../../common/components/threat_match
import type { FieldHook } from '../../../../shared_imports';
import { getFieldValidityAndErrorMessage } from '../../../../shared_imports';

interface ThreatMatchFieldProps {
export const DEFAULT_THREAT_MAPPING_VALUE = [createOrNewEntryItem()];

interface ThreatMatchMappingFieldProps {
field: FieldHook<ThreatMapEntries[]>;
threatIndexPatterns: DataViewBase;
indexPatterns: DataViewBase;
}

export function ThreatMatchField({
export function ThreatMatchMappingField({
field,
threatIndexPatterns,
indexPatterns,
}: ThreatMatchFieldProps): JSX.Element {
}: ThreatMatchMappingFieldProps): JSX.Element {
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
const { value, setValue, clearErrors } = field;
const { setValue } = field;

const handleMappingChange = useCallback(
(entryItems: ThreatMapEntries[]): void => {
if (entryItems.length === 0) {
setValue(DEFAULT_VALUE);
setValue(DEFAULT_THREAT_MAPPING_VALUE);
return;
}

Expand All @@ -40,19 +42,6 @@ export function ThreatMatchField({
[setValue]
);

useEffect(() => {
if (!Array.isArray(value) || value.length === 0) {
setValue(DEFAULT_VALUE);

// Avoid showing validation errors when setting default value
// Since Form Hook's validation is async setTimeout is required
// to clear error after validation
setTimeout(() => {
clearErrors();
});
}
}, [value, setValue, clearErrors]);

return (
<EuiFormRow
label={field.label}
Expand All @@ -73,5 +62,3 @@ export function ThreatMatchField({
</EuiFormRow>
);
}

const DEFAULT_VALUE = [createOrNewEntryItem()];
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import {
containsInvalidItems,
singleEntryThreat,
} from '../../../../../common/components/threat_match/helpers';
import type { FormData, ValidationFunc } from '../../../../../shared_imports';
import type { ThreatMapEntries } from '../../../../../common/components/threat_match/types';

export const threatMatchMappingValidator: ValidationFunc<FormData, string, ThreatMapEntries[]> = (
...args
) => {
const [{ path, value }] = args;

if (singleEntryThreat(value)) {
return {
code: 'ERR_FIELD_MISSING',
path,
message: i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredError',
{
defaultMessage: 'At least one indicator match is required.',
}
),
};
}

if (containsInvalidItems(value)) {
return {
code: 'ERR_FIELD_MISSING',
path,
message: i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customThreatQueryFieldRequiredEmptyError',
{
defaultMessage: 'All matches require both a field and threat index field.',
}
),
};
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { FieldConfig } from '../../../../shared_imports';
import { UseField } from '../../../../shared_imports';
import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar';
import { QueryBarDefineRule } from '../../../rule_creation_ui/components/query_bar';
import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory';
import { threatMatchQueryRequiredValidator } from './validators/threat_match_query_required_validator';
import { kueryValidatorFactory } from '../../../rule_creation_ui/validators/kuery_validator_factory';
import * as i18n from './translations';

Expand Down Expand Up @@ -47,7 +47,7 @@ const THREAT_MATCH_QUERY_FIELD_CONFIG: FieldConfig<FieldValueQueryBar> = {
label: i18n.THREAT_MATCH_QUERY_FIELD_LABEL,
validations: [
{
validator: queryRequiredValidatorFactory('threat_match'),
validator: threatMatchQueryRequiredValidator,
},
{
validator: kueryValidatorFactory(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { isEmpty } from 'lodash';
import type { FieldValueQueryBar } from '../../../../rule_creation_ui/components/query_bar';
import type { FormData, ValidationFunc } from '../../../../../shared_imports';
import * as i18n from './translations';

export const threatMatchQueryRequiredValidator: ValidationFunc<
FormData,
string,
FieldValueQueryBar
> = (...args) => {
const [{ path, value }] = args;

if (isEmpty(value.query.query as string) && isEmpty(value.filters)) {
return {
code: 'ERR_FIELD_MISSING',
path,
message: i18n.THREAT_MATCH_QUERY_REQUIRED_ERROR,
};
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';

export const THREAT_MATCH_QUERY_REQUIRED_ERROR = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchQueryBar.requiredError',
{
defaultMessage: 'A custom query is required.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -361,9 +361,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
}
}, [ruleType, previousRuleType, getFields]);

usePersistentThreatMatchState({
form,
});
usePersistentThreatMatchState({ form });
usePersistentAlertSuppressionState({ form, ruleTypePath: 'ruleType' });

// if saved query failed to load:
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import { DEFAULT_THREAT_MAPPING_VALUE } from '../../../../detection_engine/rule_creation/components/threat_match_mapping_edit';
import {
ALERT_SUPPRESSION_DURATION_FIELD_NAME,
ALERT_SUPPRESSION_DURATION_TYPE_FIELD_NAME,
Expand Down Expand Up @@ -56,7 +57,7 @@ export const stepDefineDefaultValue: DefineStepRule = {
},
requiredFields: [],
relatedIntegrations: [],
threatMapping: [],
threatMapping: DEFAULT_THREAT_MAPPING_VALUE,
threshold: {
field: [],
value: '200',
Expand Down

0 comments on commit f9e8e69

Please sign in to comment.