diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx index 8b2442940771..abce17b21a03 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/related_integrations/related_integrations.tsx @@ -8,55 +8,101 @@ import React from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { UseArray, useFormData } from '../../../../shared_imports'; +import type { FormHook, ArrayItem } from '../../../../shared_imports'; import { RelatedIntegrationsHelpInfo } from './related_integrations_help_info'; import { RelatedIntegrationFieldRow } from './related_integration_field_row'; import * as i18n from './translations'; import { OptionalFieldLabel } from '../optional_field_label'; +import { getFlattenedArrayFieldNames } from '../utils'; interface RelatedIntegrationsProps { path: string; dataTestSubj?: string; } -export function RelatedIntegrations({ path, dataTestSubj }: RelatedIntegrationsProps): JSX.Element { +function RelatedIntegrationsComponent({ + path, + dataTestSubj, +}: RelatedIntegrationsProps): JSX.Element { + return ( + + {({ items, addItem, removeItem, form }) => ( + + )} + + ); +} + +interface RelatedIntegrationsListProps { + items: ArrayItem[]; + addItem: () => void; + removeItem: (id: number) => void; + path: string; + form: FormHook; + dataTestSubj?: string; +} + +const RelatedIntegrationsList = ({ + items, + addItem, + removeItem, + path, + form, + dataTestSubj, +}: RelatedIntegrationsListProps) => { + const flattenedFieldNames = getFlattenedArrayFieldNames(form, path); + + /* + Not using "watch" for the initial render, to let row components render and initialize form fields. + Then we can use the "watch" feature to track their changes. + */ + const hasRenderedInitially = flattenedFieldNames.length > 0; + const fieldsToWatch = hasRenderedInitially ? flattenedFieldNames : []; + + const [formData] = useFormData({ watch: fieldsToWatch }); + const label = ( <> {i18n.RELATED_INTEGRATIONS_LABEL} ); - const [formData] = useFormData(); return ( - - {({ items, addItem, removeItem }) => ( - - <> - - {items.map((item) => ( - - - - ))} - - {items.length > 0 && } - - {i18n.ADD_INTEGRATION} - - - - )} - + + <> + + {items.map((item) => ( + + + + ))} + + {items.length > 0 && } + + {i18n.ADD_INTEGRATION} + + + ); -} +}; + +export const RelatedIntegrations = React.memo(RelatedIntegrationsComponent); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/make_validate_required_field.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/make_validate_required_field.ts index 26ddcc5f61c1..499f7fc60b2d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/make_validate_required_field.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/make_validate_required_field.ts @@ -8,7 +8,7 @@ import type { RequiredFieldInput } from '../../../../../common/api/detection_engine/model/rule_schema/common_attributes.gen'; import type { ERROR_CODE, FormData, FormHook, ValidationFunc } from '../../../../shared_imports'; import * as i18n from './translations'; -import { getFlattenedArrayFieldNames } from './utils'; +import { getFlattenedArrayFieldNames } from '../utils'; export function makeValidateRequiredField(parentFieldPath: string) { return function validateRequiredField( diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields.tsx index 27387909d330..9bfa9d45d1bb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/required_fields.tsx @@ -16,7 +16,7 @@ import { RequiredFieldsHelpInfo } from './required_fields_help_info'; import * as defineRuleI18n from '../../../rule_creation_ui/components/step_define_rule/translations'; import { OptionalFieldLabel } from '../optional_field_label'; import { RequiredFieldRow } from './required_fields_row'; -import { getFlattenedArrayFieldNames } from './utils'; +import { getFlattenedArrayFieldNames } from '../utils'; import * as i18n from './translations'; interface RequiredFieldsComponentProps { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/utils.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/utils.ts index 38820e992fa6..55beca264e12 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/required_fields/utils.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { FormHook, ArrayItem } from '../../../../shared_imports'; - interface PickTypeForNameParameters { name: string; type: string; @@ -28,26 +26,3 @@ export function pickTypeForName({ name, type, typesByFieldName = {} }: PickTypeF */ return typesAvailableForName[0] ?? type; } - -/** - * Returns a list of flattened field names for a given array field of a form. - * Flattened field name is a string that represents the path to an item in an array field. - * For example, a field "myArrayField" can be represented as "myArrayField[0]", "myArrayField[1]", etc. - * - * Flattened field names are useful: - * - when you need to subscribe to changes in an array field using `useFormData` "watch" option - * - when you need to retrieve form data before serializer function is applied - * - * @param {Object} form - Form object. - * @param {string} arrayFieldName - Path to the array field. - * @returns {string[]} - Flattened array field names. - */ -export function getFlattenedArrayFieldNames( - form: { getFields: FormHook['getFields'] }, - arrayFieldName: string -): string[] { - const internalField = form.getFields()[`${arrayFieldName}__array__`] ?? {}; - const internalFieldValue = (internalField?.value ?? []) as ArrayItem[]; - - return internalFieldValue.map((item) => item.path); -} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/utils.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/utils.ts new file mode 100644 index 000000000000..79ea06bef38c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/utils.ts @@ -0,0 +1,31 @@ +/* + * 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 type { ArrayItem, FormHook } from '../../../shared_imports'; + +/** + * Returns a list of flattened field names for a given array field of a form. + * Flattened field name is a string that represents the path to an item in an array field. + * For example, a field "myArrayField" can be represented as "myArrayField[0]", "myArrayField[1]", etc. + * + * Flattened field names are useful: + * - when you need to subscribe to changes in an array field using `useFormData` "watch" option + * - when you need to retrieve form data before serializer function is applied + * + * @param {Object} form - Form object. + * @param {string} arrayFieldName - Path to the array field. + * @returns {string[]} - Flattened array field names. + */ +export function getFlattenedArrayFieldNames( + form: { getFields: FormHook['getFields'] }, + arrayFieldName: string +): string[] { + const internalField = form.getFields()[`${arrayFieldName}__array__`] ?? {}; + const internalFieldValue = (internalField?.value ?? []) as ArrayItem[]; + + return internalFieldValue.map((item) => item.path); +}