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] FinalEdit: Add fields that are common for all rule types (PR 1) #196326

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,16 +7,62 @@

import React from 'react';
import { FieldFormWrapper } from './field_form_wrapper';
import { NameEdit, nameSchema } from './fields/name';
import type { UpgradeableCommonFields } from '../../../../model/prebuilt_rule_upgrade/fields';
import { DescriptionEdit, descriptionSchema } from './fields/description';
import {
FalsePositivesEdit,
falsePositivesSchema,
falsePositivesSerializer,
falsePositivesDeserializer,
} from './fields/false_positives';
import {
InvestigationFieldsEdit,
investigationFieldsSchema,
investigationFieldsDeserializer,
investigationFieldsSerializer,
} from './fields/investigation_fields';
import { NameEdit, nameSchema } from './fields/name';
import { ReferencesEdit, referencesSchema, referencesSerializer } from './fields/references';
import { TagsEdit, tagsSchema } from './fields/tags';

interface CommonRuleFieldEditProps {
fieldName: UpgradeableCommonFields;
}

export function CommonRuleFieldEdit({ fieldName }: CommonRuleFieldEditProps) {
switch (fieldName) {
case 'description':
return <FieldFormWrapper component={DescriptionEdit} fieldFormSchema={descriptionSchema} />;
case 'false_positives':
return (
<FieldFormWrapper
component={FalsePositivesEdit}
fieldFormSchema={falsePositivesSchema}
serializer={falsePositivesSerializer}
deserializer={falsePositivesDeserializer}
/>
);
case 'investigation_fields':
return (
<FieldFormWrapper
component={InvestigationFieldsEdit}
fieldFormSchema={investigationFieldsSchema}
serializer={investigationFieldsSerializer}
deserializer={investigationFieldsDeserializer}
/>
);
case 'name':
return <FieldFormWrapper component={NameEdit} fieldFormSchema={nameSchema} />;
case 'references':
return (
<FieldFormWrapper
component={ReferencesEdit}
fieldFormSchema={referencesSchema}
serializer={referencesSerializer}
/>
);
case 'tags':
return <FieldFormWrapper component={TagsEdit} fieldFormSchema={tagsSchema} />;
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
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 React from 'react';
import type { FormSchema } from '../../../../../../../shared_imports';
import { Field, UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type { RuleDescription } from '../../../../../../../../common/api/detection_engine';

export const descriptionSchema = { description: schema.description } as FormSchema<{
description: RuleDescription;
}>;

const componentProps = {
euiFieldProps: {
fullWidth: true,
compressed: true,
},
};

export function DescriptionEdit(): JSX.Element {
return <UseField path="description" component={Field} componentProps={componentProps} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 React from 'react';
import { compact } from 'lodash';
import * as i18n from '../../../../../../rule_creation_ui/components/step_about_rule/translations';
import type { FormSchema, FormData } from '../../../../../../../shared_imports';
import { UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type { RuleFalsePositiveArray } from '../../../../../../../../common/api/detection_engine';
import { AddItem } from '../../../../../../rule_creation_ui/components/add_item_form';

export const falsePositivesSchema = { falsePositives: schema.falsePositives } as FormSchema<{
falsePositives: RuleFalsePositiveArray;
}>;

const componentProps = {
addText: i18n.ADD_FALSE_POSITIVE,
};

export function FalsePositivesEdit(): JSX.Element {
return <UseField path="falsePositives" component={AddItem} componentProps={componentProps} />;
}

export function falsePositivesDeserializer(defaultValue: FormData) {
/* Set initial form value with camelCase "falsePositives" key instead of "false_positives" */
return {
falsePositives: defaultValue,
};
}

export function falsePositivesSerializer(formData: FormData): {
false_positives: RuleFalsePositiveArray;
} {
return {
/* Remove empty items from the falsePositives array */
false_positives: compact(formData.falsePositives),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 React from 'react';
import type { FormSchema, FormData } from '../../../../../../../shared_imports';
import { UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type {
DiffableRule,
InvestigationFields,
RuleFalsePositiveArray,
} from '../../../../../../../../common/api/detection_engine';
import { MultiSelectFieldsAutocomplete } from '../../../../../../rule_creation_ui/components/multi_select_fields';
import { useAllEsqlRuleFields } from '../../../../../../rule_creation_ui/hooks';
import { useDefaultIndexPattern } from '../../../use_default_index_pattern';
import { useRuleIndexPattern } from '../../../../../../rule_creation_ui/pages/form';
import { getUseRuleIndexPatternParameters } from '../utils';

export const investigationFieldsSchema = {
investigationFields: schema.investigationFields,
} as FormSchema<{
investigationFields: RuleFalsePositiveArray;
}>;

interface InvestigationFieldsEditProps {
finalDiffableRule: DiffableRule;
}

export function InvestigationFieldsEdit({
finalDiffableRule,
}: InvestigationFieldsEditProps): JSX.Element {
const { type } = finalDiffableRule;

const defaultIndexPattern = useDefaultIndexPattern();
const indexPatternParameters = getUseRuleIndexPatternParameters(
finalDiffableRule,
defaultIndexPattern
);
const { indexPattern, isIndexPatternLoading } = useRuleIndexPattern(indexPatternParameters);

const { fields: investigationFields, isLoading: isInvestigationFieldsLoading } =
useAllEsqlRuleFields({
esqlQuery: type === 'esql' ? finalDiffableRule.esql_query.query : undefined,
indexPatternsFields: indexPattern.fields,
});

return (
<UseField
path="investigationFields"
component={MultiSelectFieldsAutocomplete}
componentProps={{
browserFields: investigationFields,
isDisabled: isIndexPatternLoading || isInvestigationFieldsLoading,
fullWidth: true,
}}
/>
);
}

export function investigationFieldsDeserializer(defaultValue: FormData) {
/* Set initial form value with camelCase "investigationFields" key instead of "investigation_fields" */
return {
investigationFields: defaultValue?.field_names ?? [],
};
}

export function investigationFieldsSerializer(formData: FormData): {
investigation_fields: InvestigationFields | undefined;
} {
const hasInvestigationFields = formData.investigationFields.length > 0;

return {
investigation_fields: hasInvestigationFields
? {
field_names: formData.investigationFields,
}
: undefined,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type { FieldValueQueryBar } from '../../../../../../rule_creation_ui/comp
import * as stepDefineRuleI18n from '../../../../../../rule_creation_ui/components/step_define_rule/translations';
import { useRuleIndexPattern } from '../../../../../../rule_creation_ui/pages/form';
import {
DataSourceType as DataSourceTypeSnakeCase,
KqlQueryLanguage,
KqlQueryType,
RuleQuery,
Expand All @@ -32,11 +31,11 @@ import type {
SavedKqlQuery,
} from '../../../../../../../../common/api/detection_engine';
import { useDefaultIndexPattern } from '../../../use_default_index_pattern';
import { DataSourceType } from '../../../../../../../detections/pages/detection_engine/rules/types';
import { isFilters } from '../../../helpers';
import type { SetRuleQuery } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline';
import { useRuleFromTimeline } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline';
import { useGetSavedQuery } from '../../../../../../../detections/pages/detection_engine/rules/use_get_saved_query';
import { getUseRuleIndexPatternParameters } from '../utils';

export const kqlQuerySchema = {
ruleType: schema.ruleType,
Expand Down Expand Up @@ -199,37 +198,6 @@ export function kqlQueryDeserializer(
return returnValue;
}

interface UseRuleIndexPatternParameters {
dataSourceType: DataSourceType;
index: string[];
dataViewId: string | undefined;
}

function getUseRuleIndexPatternParameters(
finalDiffableRule: DiffableRule,
defaultIndexPattern: string[]
): UseRuleIndexPatternParameters {
if (!('data_source' in finalDiffableRule) || !finalDiffableRule.data_source) {
return {
dataSourceType: DataSourceType.IndexPatterns,
index: defaultIndexPattern,
dataViewId: undefined,
};
}
if (finalDiffableRule.data_source.type === DataSourceTypeSnakeCase.data_view) {
return {
dataSourceType: DataSourceType.DataView,
index: [],
dataViewId: finalDiffableRule.data_source.data_view_id,
};
}
return {
dataSourceType: DataSourceType.IndexPatterns,
index: finalDiffableRule.data_source.index_patterns,
dataViewId: undefined,
};
}

function getSavedQueryId(diffableRule: DiffableRule): string | undefined {
if (diffableRule.type === 'saved_query' && 'saved_query_id' in diffableRule.kql_query) {
return diffableRule.kql_query.saved_query_id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@ import type { RuleName } from '../../../../../../../../common/api/detection_engi

export const nameSchema = { name: schema.name } as FormSchema<{ name: RuleName }>;

const componentProps = {
euiFieldProps: {
fullWidth: true,
},
};

export function NameEdit(): JSX.Element {
return (
<UseField
path="name"
component={Field}
componentProps={{
euiFieldProps: {
fullWidth: true,
},
}}
/>
);
return <UseField path="name" component={Field} componentProps={componentProps} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 React from 'react';
import { compact } from 'lodash';
import * as i18n from '../../../../../../rule_creation_ui/components/step_about_rule/translations';
import type { FormSchema, FormData } from '../../../../../../../shared_imports';
import { UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type { RuleReferenceArray } from '../../../../../../../../common/api/detection_engine';
import { AddItem } from '../../../../../../rule_creation_ui/components/add_item_form';
import { isUrlInvalid } from '../../../../../../../common/utils/validators';

export const referencesSchema = { references: schema.references } as FormSchema<{
references: RuleReferenceArray;
}>;

const componentProps = {
addText: i18n.ADD_REFERENCE,
validate: isUrlInvalid,
};

export function ReferencesEdit(): JSX.Element {
return <UseField path="references" component={AddItem} componentProps={componentProps} />;
}

export function referencesSerializer(formData: FormData): {
references: RuleReferenceArray;
} {
return {
/* Remove empty items from the references array */
references: compact(formData.references),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 React from 'react';
import type { FormSchema } from '../../../../../../../shared_imports';
import { Field, UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type { RuleTagArray } from '../../../../../../../../common/api/detection_engine';

export const tagsSchema = { tags: schema.tags } as FormSchema<{ tags: RuleTagArray }>;

const componentProps = {
euiFieldProps: {
fullWidth: true,
placeholder: '',
},
};

export function TagsEdit(): JSX.Element {
return <UseField path="tags" component={Field} componentProps={componentProps} />;
}
Loading